diff --git a/README.md b/README.md index 693d246..ad84658 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,7 @@ A virtual host scanner that can be used with pivot tools, detect catch-all scena * Work over HTTP and HTTPS * Ability to set the real port of the webserver to use in headers when pivoting through ssh/nc * Add simple response headers to bypass some WAF products +* Identify new targets by using reverse lookups and append to wordlist ## Product Comparisons @@ -40,6 +41,8 @@ $ pip install -r requirements.txt | --unique-depth UNIQUE_DEPTH | Show likely matches of page content that is found x times (default 1). | | --ssl | If set then connections will be made over HTTPS instead of HTTP. | | --fuzzy-logic | If set then all unique content replies are compared and a similarity ratio is given for each pair. This helps to isolate vhosts in situations where a default page isn't static (such as having the time on it). | +| --no-lookups | Disbale reverse lookups (identifies new targets and append to wordlist, on by default). | +| --rate-limit | Amount of time in seconds to delay between each scan (default 0). | | --waf | If set then simple WAF bypass headers will be sent. | | -oN OUTPUT_NORMAL | Normal output printed to a file when the -oN option is specified with a filename argument. | | - | By passing a blank '-' you tell VHostScan to expect input from stdin (pipe). | diff --git a/VHostScan.py b/VHostScan.py index 1ad7d7a..b387518 100644 --- a/VHostScan.py +++ b/VHostScan.py @@ -3,6 +3,8 @@ import os import sys from argparse import ArgumentParser +from dns.resolver import Resolver +from socket import gethostbyaddr from lib.core.virtual_host_scanner import * from lib.helpers.output_helper import * from lib.helpers.file_helper import get_combined_word_lists @@ -29,10 +31,12 @@ def main(): parser.add_argument('--unique-depth', dest='unique_depth', type=int, help='Show likely matches of page content that is found x times (default 1).', default=1) parser.add_argument("--ssl", dest="ssl", action="store_true", help="If set then connections will be made over HTTPS instead of HTTP (default http).", default=False) parser.add_argument("--fuzzy-logic", dest="fuzzy_logic", action="store_true", help="If set then fuzzy match will be performed against unique hosts (default off).", default=False) + parser.add_argument("--no-lookups", dest="no_lookup", action="store_true", help="Disable reverse lookups (identifies new targets and appends to wordlist, on by default).", default=False) + parser.add_argument("--rate-limit", dest="rate_limit", type=int, help='Amount of time in seconds to delay between each scan (default 0).', default=0) parser.add_argument("--waf", dest="add_waf_bypass_headers", action="store_true", help="If set then simple WAF bypass headers will be sent.", default=False) parser.add_argument("-oN", dest="output_normal", help="Normal output printed to a file when the -oN option is specified with a filename argument." ) parser.add_argument("-", dest="stdin", action="store_true", help="By passing a blank '-' you tell VHostScan to expect input from stdin (pipe).", default=False) - + arguments = parser.parse_args() wordlist = [] @@ -71,8 +75,16 @@ def main(): if(arguments.ignore_content_length > 0): print("[>] Ignoring Content length: %s" % (arguments.ignore_content_length)) - scanner = virtual_host_scanner( arguments.target_hosts, arguments.base_host, wordlist, arguments.port, arguments.real_port, arguments.ssl, - arguments.unique_depth, arguments.ignore_http_codes, arguments.ignore_content_length, arguments.fuzzy_logic, arguments.add_waf_bypass_headers) + if not arguments.no_lookup: + for ip in Resolver().query(arguments.target_hosts, 'A'): + host, aliases, ips = gethostbyaddr(str(ip)) + wordlist.append(str(ip)) + wordlist.append(host) + wordlist.extend(aliases) + + scanner_args = vars(arguments) + scanner_args.update({'target': arguments.target_hosts, 'wordlist': wordlist}) + scanner = virtual_host_scanner(**scanner_args) scanner.scan() output = output_helper(scanner, arguments) diff --git a/lib/core/virtual_host_scanner.py b/lib/core/virtual_host_scanner.py index 0fd5c56..d84123a 100644 --- a/lib/core/virtual_host_scanner.py +++ b/lib/core/virtual_host_scanner.py @@ -2,10 +2,10 @@ import requests import hashlib import pandas as pd +import time from lib.core.discovered_host import * - class virtual_host_scanner(object): """Virtual host scanning class @@ -19,19 +19,21 @@ class virtual_host_scanner(object): ignore_content_length: integer value of content length to ignore output: folder to write output file to """ - - def __init__(self, target, base_host, wordlist, port=80, real_port=80, ssl=False, unique_depth=1, ignore_http_codes='404', ignore_content_length=0, fuzzy_logic=False, add_waf_bypass_headers=False): + + + def __init__(self, target, wordlist, **kwargs): self.target = target - self.base_host = base_host - self.port = int(port) - self.real_port = int(real_port) - self.ignore_http_codes = list(map(int, ignore_http_codes.replace(' ', '').split(','))) - self.ignore_content_length = ignore_content_length self.wordlist = wordlist - self.unique_depth = unique_depth - self.ssl = ssl - self.fuzzy_logic = fuzzy_logic - self.add_waf_bypass_headers = add_waf_bypass_headers + self.base_host = kwargs.get('base_host') + self.rate_limit = int(kwargs.get('rate_limit', 0)) + self.port = int(kwargs.get('port', 80)) + self.real_port = int(kwargs.get('real_port', 80)) + self.ignore_content_length = int(kwargs.get('ignore_content_length', 0)) + self.ssl = kwargs.get('ssl', False) + self.fuzzy_logic = kwargs.get('fuzzy_logic', False) + self.add_waf_bypass_headers = kwargs.get('add_waf_bypass_headers', False) + self.unique_depth = int(kwargs.get('unique_depth', 1)) + self.ignore_http_codes = kwargs.get('ignore_http_codes', '404') # this can be made redundant in future with better exceptions self.completed_scan=False @@ -42,6 +44,14 @@ def __init__(self, target, base_host, wordlist, port=80, real_port=80, ssl=False # store associated data for discovered hosts in array for oN, oJ, etc' self.hosts = [] + @property + def ignore_http_codes(self): + return self._ignore_http_codes + + @ignore_http_codes.setter + def ignore_http_codes(self, codes): + self._ignore_http_codes = [int(code) for code in codes.replace(' ', '').split(',')] + def scan(self): if not self.base_host: @@ -104,6 +114,9 @@ def scan(self): # add url and hash into array for likely matches self.results.append(hostname + ',' + page_hash) + + #rate limit the connection, if the int is 0 it is ignored + time.sleep(self.rate_limit) self.completed_scan=True