From 199f1cde12fabcdf0746c4549d3be54dc4ea2edb Mon Sep 17 00:00:00 2001 From: evyatarmeged Date: Thu, 2 Aug 2018 22:02:30 +0300 Subject: [PATCH] refactoring --- README.md | 6 ++-- raccoon_src/lib/dns_handler.py | 2 +- raccoon_src/lib/fuzzer.py | 64 ++++++++++++++++++++-------------- raccoon_src/lib/sub_domain.py | 2 +- raccoon_src/main.py | 17 ++++----- setup.py | 2 +- tests/test_fuzzer.py | 4 +-- 7 files changed, 54 insertions(+), 43 deletions(-) diff --git a/README.md b/README.md index d9a20e4..ce61ff6 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ![Build Status](https://travis-ci.org/evyatarmeged/Raccoon.svg?branch=master) ![license](https://img.shields.io/github/license/mashape/apistatus.svg) ![pythonver](https://img.shields.io/badge/python-3.5%2B-blue.svg) -![raccoonver](https://img.shields.io/badge/Raccoon%20version-0.0.74-lightgrey.svg) +![raccoonver](https://img.shields.io/badge/Raccoon%20version-0.0.75-lightgrey.svg) ##### Features - [x] DNS details @@ -109,9 +109,11 @@ Options: the default --tls-port INTEGER Use this port for TLS queries. Default: 443 --skip-health-check Do not test for target host availability - -fr, --follow-redirects Follow redirects when fuzzing. Default: True + --follow-redirects Follow redirects when fuzzing. Default: False + (will not follow redirects) --no-url-fuzzing Do not fuzz URLs --no-sub-enum Do not bruteforce subdomains + --skip-nmap-scan Do not perform an Nmap scan -q, --quiet Do not output to stdout -o, --outdir TEXT Directory destination for scan output --help Show this message and exit. diff --git a/raccoon_src/lib/dns_handler.py b/raccoon_src/lib/dns_handler.py index 000cb14..c55999a 100644 --- a/raccoon_src/lib/dns_handler.py +++ b/raccoon_src/lib/dns_handler.py @@ -57,7 +57,7 @@ async def grab_whois(cls, host): logger.debug(line) @classmethod - def generate_dns_dumpster_mapping(cls, host, sout_logger): + async def generate_dns_dumpster_mapping(cls, host, sout_logger): sout_logger.info("{} Trying to fetch DNS Mapping for {} from DNS dumpster".format( COLORED_COMBOS.INFO, host)) try: diff --git a/raccoon_src/lib/fuzzer.py b/raccoon_src/lib/fuzzer.py index 9b98416..d0f6b5a 100644 --- a/raccoon_src/lib/fuzzer.py +++ b/raccoon_src/lib/fuzzer.py @@ -18,7 +18,7 @@ def __init__(self, host, ignored_response_codes, num_threads, - wordlist, + path_to_wordlist, follow_redirects=False): self.target = host.target @@ -26,11 +26,22 @@ def __init__(self, self.proto = host.protocol self.port = host.port self.num_threads = num_threads - self.wordlist = wordlist + self.path_to_wordlist = path_to_wordlist + self.wordlist = self._create_set_from_wordlist_file(path_to_wordlist) self.follow_redirects = follow_redirects self.request_handler = RequestHandler() # Will get the single, already initiated instance self.logger = None + @staticmethod + def _create_set_from_wordlist_file(wordlist): + try: + with open(wordlist, "r") as file: + fuzzlist = file.readlines() + fuzzlist = [x.replace("\n", "") for x in fuzzlist] + return set(fuzzlist) + except FileNotFoundError: + raise FuzzerException("Cannot open file in {}. Will not perform Fuzzing".format(wordlist)) + def _log_response(self, code, url, headers): if 300 > code >= 200: color = COLOR.GREEN @@ -81,53 +92,54 @@ def get_log_file_path(self, path): return Logger(HelpUtilities.get_output_path(log_file)) - def _rule_out_false_positives(self, sub_domain): - fake_uris = (uuid.uuid4() for i in range(3)) + @staticmethod + def _rule_out_false_positives(response_codes, sub_domain): + if any(code == 200 for code in response_codes): + if sub_domain: + err_msg = "Wildcard subdomain support detected (all subdomains return 200)." \ + " Will not bruteforce subdomains" + else: + err_msg = "Web server seems to redirect requests for all resources " \ + "to eventually return 200. Will not bruteforce URLs" + raise FuzzerException(err_msg) + + def _generate_fake_requests(self, sub_domain): + response_codes = [] + fake_uris = (uuid.uuid4(), uuid.uuid4()) + session = self.request_handler.get_new_session() for uri in fake_uris: url = self._build_request_url(uri, sub_domain) try: res = self.request_handler.send("GET", url=url, allow_redirects=self.follow_redirects) - if res.status_code == 200: - if sub_domain: - err_msg = "Wildcard subdomain support detected (all subdomains return 200)." \ - " Will not bruteforce subdomains" - else: - err_msg = "Web server seems to redirect requests for all resources " \ - "to eventually return 200. Will not bruteforce URLs" - raise FuzzerException(err_msg) - + response_codes.append(res.status_code) + res = session.get(url=url, allow_redirects=self.follow_redirects) + response_codes.append(res.status_code) except RequestHandlerException as e: if sub_domain: # If should-not-work.example.com doesn't resolve, no wildcard subdomain is present - return + return [0] else: raise FuzzerException("Could not get a response from {}." " Maybe target is down ?".format(self.target)) + return response_codes async def fuzz_all(self, sub_domain=False, log_file_path=None): """ - Create a pool of threads, read the wordlist and invoke fuzz_all. + Create a pool of threads and exhaust self.wordlist on self._fetch Should be run in an event loop. :param sub_domain: Indicate if this is subdomain enumeration or URL busting :param log_file_path: Log subdomain enum results to this path. """ - self.logger = self.get_log_file_path(log_file_path) - try: - with open(self.wordlist, "r") as file: - fuzzlist = file.readlines() - fuzzlist = [x.replace("\n", "") for x in fuzzlist] - except FileNotFoundError: - raise FuzzerException("Cannot read URL list from {}. Will not perform Fuzzing".format(self.wordlist)) - try: # Rule out wildcard subdomain support/all resources redirect to a 200 page - self._rule_out_false_positives(sub_domain) + response_codes = self._generate_fake_requests(sub_domain) + self._rule_out_false_positives(response_codes, sub_domain) if not sub_domain: self.logger.info("{} Fuzzing URLs".format(COLORED_COMBOS.INFO)) - self.logger.info("{} Reading from list: {}".format(COLORED_COMBOS.INFO, self.wordlist)) + self.logger.info("{} Reading from list: {}".format(COLORED_COMBOS.INFO, self.path_to_wordlist)) pool = ThreadPool(self.num_threads) - pool.map(partial(self._fetch, sub_domain=sub_domain), fuzzlist) + pool.map(partial(self._fetch, sub_domain=sub_domain), self.wordlist) if not sub_domain: self.logger.info("{} Done fuzzing URLs".format(COLORED_COMBOS.INFO)) diff --git a/raccoon_src/lib/sub_domain.py b/raccoon_src/lib/sub_domain.py index 8a074d3..109ae3f 100644 --- a/raccoon_src/lib/sub_domain.py +++ b/raccoon_src/lib/sub_domain.py @@ -91,7 +91,7 @@ async def bruteforce(self): self.logger.info("{} Bruteforcing subdomains".format(COLORED_COMBOS.NOTIFY)) sub_domain_fuzzer = URLFuzzer( host=self.host, - wordlist=self.domain_list, + path_to_wordlist=self.domain_list, num_threads=self.num_threads, ignored_response_codes=self.ignored_error_codes, follow_redirects=self.follow_redirects diff --git a/raccoon_src/main.py b/raccoon_src/main.py index a45bb23..a42813e 100644 --- a/raccoon_src/main.py +++ b/raccoon_src/main.py @@ -43,7 +43,7 @@ def intro(logger): @click.command() -@click.version_option("0.0.74") +@click.version_option("0.0.75") @click.option("-t", "--target", required=True, help="Target to scan") @click.option("-d", "--dns-records", default="A,MX,NS,CNAME,SOA,TXT", help="Comma separated DNS records to query. Defaults to: A,MX,NS,CNAME,SOA,TXT") @@ -68,11 +68,11 @@ def intro(logger): @click.option("-p", "--port", help="Use this port range for Nmap scan instead of the default") @click.option("--tls-port", default=443, help="Use this port for TLS queries. Default: 443") @click.option("--skip-health-check", is_flag=True, help="Do not test for target host availability") -@click.option("--no-redirects", is_flag=True, - help="Do not follow redirects when fuzzing. Default: False (will follow redirects)") +@click.option("--follow-redirects", is_flag=True, default=False, + help="Follow redirects when fuzzing. Default: False (will not follow redirects)") @click.option("--no-url-fuzzing", is_flag=True, help="Do not fuzz URLs") @click.option("--no-sub-enum", is_flag=True, help="Do not bruteforce subdomains") -@click.option("--skip-nmap-scan", is_flag=True, help="Do not scan with Nmap") +@click.option("--skip-nmap-scan", is_flag=True, help="Do not perform an Nmap scan") # @click.option("-d", "--delay", default="0.25-1", # help="Min and Max number of seconds of delay to be waited between requests\n" # "Defaults to Min: 0.25, Max: 1. Specified in the format of Min-Max") @@ -94,7 +94,7 @@ def main(target, port, tls_port, skip_health_check, - no_redirects, + follow_redirects, no_url_fuzzing, no_sub_enum, skip_nmap_scan, @@ -134,7 +134,6 @@ def main(target, dns_records = tuple(dns_records.split(",")) ignored_response_codes = tuple(int(code) for code in ignored_response_codes.split(",")) - follow_redirects = not no_redirects if port: HelpUtilities.validate_port_range(port) @@ -187,14 +186,12 @@ def main(target, asyncio.ensure_future(tls_info_scanner.run()), asyncio.ensure_future(waf.detect()), asyncio.ensure_future(DNSHandler.grab_whois(host)), - asyncio.ensure_future(web_app_scanner.run_scan()) + asyncio.ensure_future(web_app_scanner.run_scan()), + asyncio.ensure_future(DNSHandler.generate_dns_dumpster_mapping(host, logger)) ) main_loop.run_until_complete(asyncio.wait(tasks)) - # DNS dumpster visualization - DNSHandler.generate_dns_dumpster_mapping(host, logger) - # Second set of checks - URL fuzzing, Subdomain enumeration if not no_url_fuzzing: fuzzer = URLFuzzer(host, ignored_response_codes, threads, wordlist, follow_redirects) diff --git a/setup.py b/setup.py index b3d561a..d6e73fa 100644 --- a/setup.py +++ b/setup.py @@ -8,7 +8,7 @@ name='raccoon-scanner', packages=find_packages(exclude="tests"), license="MIT", - version='0.0.74', + version='0.0.75', description='Offensive Security Tool for Reconnaissance and Information Gathering', long_description=long_description, long_description_content_type="text/markdown", diff --git a/tests/test_fuzzer.py b/tests/test_fuzzer.py index 17cc490..d0b4171 100644 --- a/tests/test_fuzzer.py +++ b/tests/test_fuzzer.py @@ -17,7 +17,7 @@ def setUp(self): def test_bad_wordlist(self): host = self.TestHost("127.0.0.1", ()) - fuzzer = self.TestFuzzer(host, (), wordlist="no/such/path", num_threads=1) with self.assertRaises(FuzzerException): - self.loop.run_until_complete(fuzzer.fuzz_all()) + fuzzer = self.TestFuzzer(host, (), path_to_wordlist="no/such/path", num_threads=1) +