Skip to content

Commit

Permalink
refactoring
Browse files Browse the repository at this point in the history
  • Loading branch information
evyatarmeged committed Aug 2, 2018
1 parent ec5d826 commit 199f1cd
Show file tree
Hide file tree
Showing 7 changed files with 54 additions and 43 deletions.
6 changes: 4 additions & 2 deletions README.md
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion raccoon_src/lib/dns_handler.py
Expand Up @@ -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:
Expand Down
64 changes: 38 additions & 26 deletions raccoon_src/lib/fuzzer.py
Expand Up @@ -18,19 +18,30 @@ def __init__(self,
host,
ignored_response_codes,
num_threads,
wordlist,
path_to_wordlist,
follow_redirects=False):

self.target = host.target
self.ignored_error_codes = ignored_response_codes
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
Expand Down Expand Up @@ -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))
Expand Down
2 changes: 1 addition & 1 deletion raccoon_src/lib/sub_domain.py
Expand Up @@ -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
Expand Down
17 changes: 7 additions & 10 deletions raccoon_src/main.py
Expand Up @@ -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")
Expand All @@ -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")
Expand All @@ -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,
Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion setup.py
Expand Up @@ -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",
Expand Down
4 changes: 2 additions & 2 deletions tests/test_fuzzer.py
Expand Up @@ -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)


0 comments on commit 199f1cd

Please sign in to comment.