#!/usr/bin/env python # pylint: disable=invalid-name """ @TODO - results summary """ """ --- dENUMerator --- by bl4de | bloorq@gmail.com | HackerOne: bl4de Enumerates list of subdomains (output from tools like Sublist3r or subbrute) and creates output file with servers responding on port 80/HTTP This indicates (in most caes) working webserver usage: $ ./denumerator.py [domain_list_file] """ import argparse import json import os import subprocess import time from datetime import datetime import requests welcome = """ --- dENUMerator --- usage: $ ./denumerator.py -f DOMAINS_LIST -t 5 """ DEFAULT_DIRECTORY = 'report' colors = { "white": '\33[37m', 200: '\33[32m', 204: '\33[32m', 206: '\33[32m', 301: '\33[33m', 302: '\33[33m', 303: '\33[33m', 304: '\33[33m', 401: '\33[94m', 403: '\33[94m', 404: '\33[94m', 405: '\33[94m', 411: '\33[94m', 412: '\33[94m', 415: '\33[94m', 422: '\33[94m', 429: '\33[94m', 500: '\33[31m', "magenta": '\33[35m', "cyan": '\33[36m', "grey": '\33[90m', "lightgrey": '\33[37m' } requests.packages.urllib3.disable_warnings() timeout = 2 nmap = True element_class_name_iterator = 1 def usage(): """ prints welcome message """ print(welcome) def create_output_header(html_output): html = """ denumerator output
""" html_output.write(html) return def append_to_output(html_output, url, http_status_code, response_headers, nmap_output, ip_addresses, output_directory): global element_class_name_iterator screenshot_name = url.replace('https', '').replace( 'http', '').replace('://', '') + '.png' screenshot_cmd = '/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --headless --user-agent="HackerOne" --disable-gpu --dns-prefetch-disable --log-level=0 --timeout=30000 --virtual-time-budget=999999 --run-all-compositor-stages-before-draw --screenshot={} '.format( './reports/{}/'.format(output_directory) + screenshot_name) # os.system(screenshot_cmd + url) subprocess.run( screenshot_cmd + url, shell=True, timeout=30 ) # base color for all responses http_status_code_color = "000" # green - 200 OK if http_status_code == 200: http_status_code_color = "0c0" # red - error responses, but HTTP server exists if http_status_code in [403, 415, 422, 500]: http_status_code_color = "c00" # IP address information ip_html = "
" ips = [ip for ip in ip_addresses.split(b"\n")] for ip in ips: if ip.find(b"address") > 0: ip_html = ip_html + "

IP: {}

".format( ip.split(b"address")[1].decode("utf-8")) ip_html = ip_html + "
" nmap_html = "
" if nmap == True: # nmap scan results open_ports = [port for port in nmap_output.stdout.split( b"\n") if port.find(b"open") > 0] for port in open_ports: nmap_html = nmap_html + \ "

{}

".format( port.decode("utf-8")) nmap_html = nmap_html + "
" # HTTP response headers response_headers_html = "" for header in response_headers.keys(): response_headers_html = response_headers_html + "

{} : {}

".format( header, response_headers[header] ) element_class_name = 'result_{}'.format(element_class_name_iterator) element_class_name_iterator += 1 html = """ """.format( (http_status_code // 100), http_status_code_color, element_class_name, http_status_code, url, url, (http_status_code // 100), element_class_name, screenshot_name, screenshot_name, response_headers_html, ip_html, nmap_html ) html_output.write(html) html_output.flush() return def create_output_footer(html_output): html = """

HTTP Response Status: {} {}

HTTP Response Headers

{}

IP host addresses

{}

nmap scan results

{}
""" html_output.write(html) return def send_request(proto, domain, output_file, html_output, allowed_http_responses, nmap_output, ip, output_directory): """ sends request to check if server is alive """ protocols = { 'http': 'http://', 'https': 'https://' } print('\t--> {}{}{}{}'.format(colors['magenta'], protocols.get(proto.lower()), domain, colors['white'])) resp = requests.get(protocols.get(proto.lower()) + domain, timeout=timeout, allow_redirects=False, verify=False, headers={'Host': domain}) if str(resp.status_code) in allowed_http_responses: print('[+] {} {}HTTP {}{}:\t {}'.format( datetime.now().strftime("%H:%M:%S"), colors[resp.status_code], resp.status_code, colors['white'], domain)) if str(resp.status_code) in allowed_http_responses: append_to_output(html_output, protocols.get( proto.lower()) + domain, resp.status_code, resp.headers, nmap_output, ip, output_directory) if output_file: output_file.write('{}\n'.format(domain)) output_file.flush() return resp.status_code def enumerate_domains(domains, output_file, html_output, allowed_http_responses, nmap_top_ports, output_directory, show=False): """ enumerates domain from domains """ iterator = 0 number_of_domains = len(domains) for d in domains: iterator = iterator + 1 try: d = d.strip('\n').strip('\r') print('\n{}[+] Checking domain {} from {}...{}'.format(colors['grey'], iterator, number_of_domains, colors['white'])) # IP address ip = subprocess.run( ["host", d], capture_output=True, timeout=15).stdout nmap_output = '' if nmap == True: # perform nmap scan nmap_output = subprocess.run( ["nmap", "--top-ports", str(nmap_top_ports), "-n", d], capture_output=True) print('{} nmap: '.format(colors['grey']), [port.decode("utf-8") for port in nmap_output.stdout.split(b"\n") if port.find(b"open") > 0], '{}'.format(colors['white'])) send_request('http', d, output_file, html_output, allowed_http_responses, nmap_output, ip, output_directory) time.sleep(1) send_request('https', d, output_file, html_output, allowed_http_responses, nmap_output, ip, output_directory) time.sleep(1) except requests.exceptions.InvalidURL: if show is True: print('[-] {} is not a valid URL :/'.format(d)) except requests.exceptions.ConnectTimeout: if show is True: print('[-] {} :('.format(d)) continue except requests.exceptions.ConnectionError: if show is True: print('[-] connection to {} aborted :/'.format(d)) except requests.exceptions.ReadTimeout: if show is True: print('[-] {} read timeout :/'.format(d)) except requests.exceptions.TooManyRedirects: if show is True: print('[-] {} probably went into redirects loop :('.format(d)) except UnicodeError: pass except subprocess.TimeoutExpired: pass else: pass def enumerate_from_crt_sh(domain): ''' Perform subdomains enumeration using crt.sh service ''' base_url = "https://crt.sh/?q={}&output=json".format(domain) data = {} enumerated_subdomains = [] resp = requests.get(base_url) if resp.status_code == 200: data = json.loads(resp.content.decode('utf-8')) for elem in data: if elem['common_name'] not in enumerated_subdomains: enumerated_subdomains.append(elem['common_name']) if len(enumerated_subdomains) > 0: print("{}[+] Done! Found {} subdomains, performing HTTP servers enumeration...{}".format( colors['cyan'], len(enumerated_subdomains), colors['white'])) return enumerated_subdomains else: exit("[-] No data retrieved for domain {}".format(domain)) def main(): parser = argparse.ArgumentParser() allowed_http_responses = [] parser.add_argument( "-f", "--file", help="File with list of hostnames to check (-t/--target will be ignored)") parser.add_argument( "-t", "--target", help="Target domain - will use crt.sh to perform subdomain enumeration (-f/--file will be ignored)") parser.add_argument( "-s", "--success", help="Show all responses, including exceptions", action='store_true') parser.add_argument( "-o", "--output", help="Path to text output file with all domains with identified web servers") parser.add_argument( "-d", "--dir", help="Output directory name (default: report/)") parser.add_argument( "-c", "--code", help="Show only selected HTTP response status codes, comma separated", default='200,206,301,302,403,422,500' ) parser.add_argument( "-n", "--nmap", help="use nmap for port scanning (slows down the whole enumeration A LOT, so be warned!)", action='store_true' ) parser.add_argument( "-p", "--ports", help="--top-ports option for nmap (default = 100)", default=100 ) args = parser.parse_args() if args.nmap: nmap = True if args.dir: output_directory = args.dir else: output_directory = DEFAULT_DIRECTORY if args.code: allowed_http_responses = args.code.split(',') else: allowed_http_responses = ['200', '301', '500'] nmap_top_ports = args.ports # set options show = True if args.success else False # use provided file with list of hostnames or perform subdomain enumeration with crt.sh: if args.target is None and args.file is not None and os.path.isfile(args.file): domains = open(args.file, 'r').readlines() elif args.target is not None and args.file is None: domains = enumerate_from_crt_sh(args.target) else: exit('[-] No file with hostnames or domain to recon. Use either -f or -t option') # create dir for HTML report if os.path.isdir('reports') == False: os.mkdir('reports') if os.path.isdir('reports/{}'.format(output_directory)) == False: os.mkdir('reports/{}'.format(output_directory)) # starts output HTML html_output = open( 'reports/{}/__denumerator_report.html'.format(output_directory), 'w+') create_output_header(html_output) # if output filename was specified, create it and use to write report result if args.output: output_filename = os.path.join( 'reports', output_directory, args.output) output_file = open(output_filename, 'w+') else: output_file = open('__enumerated_domains.txt', 'w+') # main loop enumerate_domains(domains, output_file, html_output, allowed_http_responses, nmap_top_ports, output_directory, show) # finish HTML output create_output_footer(html_output) html_output.close() # close output file if args.output: output_file.close() if __name__ == "__main__": main()