Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add first hit flag to return first successful result. #63

Merged
merged 5 commits into from
Oct 8, 2017
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ $ pip install -r requirements.txt
| -r REAL_PORT | The real port of the webserver to use in headers when not 80 (see RFC2616 14.23), useful when pivoting through ssh/nc etc (default to PORT). |
| --ignore-http-codes IGNORE_HTTP_CODES | Comma separated list of http codes to ignore with virtual host scans (default 404). |
| --ignore-content-length IGNORE_CONTENT_LENGTH | Ignore content lengths of specificed amount. |
| --first-hit | Return first successful result. Only use in scenarios where you are sure no catch-all is configured (such as a CTF). |
| --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). |
Expand All @@ -51,6 +52,7 @@ $ pip install -r requirements.txt
| -oJ OUTPUT_JSON | JSON output printed to a file when the -oJ option is specified with a filename argument. |
| - | By passing a blank '-' you tell VHostScan to expect input from stdin (pipe). |


## Usage Examples

_Note that a number of these examples reference 10.10.10.29. This IP refers to BANK.HTB, a retired target machine from HackTheBox (https://www.hackthebox.eu/)._
Expand Down Expand Up @@ -98,4 +100,4 @@ pip install -r test-requirements.txt
pytest
```

If you're thinking of adding a new feature to the project, consider also contributing with a couple of tests. A well-tested codebase is a sane codebase. :)
If you're thinking of adding a new feature to the project, consider also contributing with a couple of tests. A well-tested codebase is a sane codebase. :)
16 changes: 10 additions & 6 deletions VHostScan.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ def main():

parser.add_argument('--ignore-http-codes', dest='ignore_http_codes', type=str, help='Comma separated list of http codes to ignore with virtual host scans (default 404).', default='404')
parser.add_argument('--ignore-content-length', dest='ignore_content_length', type=int, help='Ignore content lengths of specificed amount (default 0).', default=0)
parser.add_argument('--first-hit', dest='first_hit', action='store_true', help='Return first successful result. Only use in scenarios where you are sure no catch-all is configured (such as a CTF).', default=False)
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)
Expand All @@ -40,7 +41,7 @@ def main():
parser.add_argument("-oJ", dest="output_json", help="JSON output printed to a file when the -oJ 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()
arguments = parser.parse_args()
wordlist = []

word_list_types = []
Expand Down Expand Up @@ -69,23 +70,26 @@ def main():

user_agents = []
if arguments.user_agent:
print('[>] User-Agent specified, using it')
print('[>] User-Agent specified, using it.')
user_agents = [arguments.user_agent]
elif arguments.random_agent:
print('[>] Random User-Agent flag set')
print('[>] Random User-Agent flag set.')
user_agents = load_random_user_agents()

if(arguments.ssl):
print("[>] SSL flag set, sending all results over HTTPS")
print("[>] SSL flag set, sending all results over HTTPS.")

if(arguments.add_waf_bypass_headers):
print("[>] WAF flag set, sending simple WAF bypass headers")
print("[>] WAF flag set, sending simple WAF bypass headers.")

print("[>] Ignoring HTTP codes: %s" % (arguments.ignore_http_codes))

if(arguments.ignore_content_length > 0):
print("[>] Ignoring Content length: %s" % (arguments.ignore_content_length))

if arguments.first_hit:
print("[>] First hit is set.")

if not arguments.no_lookup:
for ip in Resolver().query(arguments.target_hosts, 'A'):
host, aliases, ips = gethostbyaddr(str(ip))
Expand Down
2 changes: 1 addition & 1 deletion lib/core/__version__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@
# |V|H|o|s|t|S|c|a|n| Developed by @codingo_ & @__timk
# +-+-+-+-+-+-+-+-+-+ https://github.com/codingo/VHostScan

__version__ = '1.5.2'
__version__ = '1.6'

49 changes: 30 additions & 19 deletions lib/core/virtual_host_scanner.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@

class virtual_host_scanner(object):
"""Virtual host scanning class

Virtual host scanner has the following properties:

Attributes:
wordlist: location to a wordlist file to use with scans
target: the target for scanning
Expand All @@ -38,6 +38,7 @@ def __init__(self, target, wordlist, **kwargs):
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')
self.first_hit = kwargs.get('first_hit')

# this can be made redundant in future with better exceptions
self.completed_scan=False
Expand Down Expand Up @@ -99,26 +100,15 @@ def scan(self):

# hash the page results to aid in identifing unique content
page_hash = hashlib.sha256(res.text.encode('utf-8')).hexdigest()
output = '[#] Found: {} (code: {}, length: {}, hash: {})\n'.format(hostname, res.status_code,
res.headers.get('content-length'), page_hash)
host = discovered_host()
host.hostname = hostname
host.response_code = res.status_code
host.hash = page_hash
host.content = res.content

for key, val in res.headers.items():
output += ' {}: {}\n'.format(key, val)
host.keys.append('{}: {}'.format(key, val))

self.hosts.append(host)

# print current results so feedback remains in "realtime"
print(output)

self.hosts.append(self.create_host(res, hostname, page_hash))

# add url and hash into array for likely matches
self.results.append(hostname + ',' + page_hash)


if len(self.hosts) == 2 and self.first_hit:
break

#rate limit the connection, if the int is 0 it is ignored
time.sleep(self.rate_limit)

Expand All @@ -141,3 +131,24 @@ def likely_matches(self):
matches = ((segmented_data["key_col"].values).tolist())

return matches

def create_host(self, response, hostname, page_hash):
"""
Creates a host using the responce and the hash.
Prints current result in real time.
"""
output = '[#] Found: {} (code: {}, length: {}, hash: {})\n'.format(hostname, response.status_code,
response.headers.get('content-length'), page_hash)
host = discovered_host()
host.hostname = hostname
host.response_code = response.status_code
host.hash = page_hash
host.content = response.content

for key, val in response.headers.items():
output += ' {}: {}\n'.format(key, val)
host.keys.append('{}: {}'.format(key, val))

print(output)

return host
1 change: 0 additions & 1 deletion tests/helpers/test_file_helper.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,4 +39,3 @@ def test_get_combined_word_lists(wordlist):

assert wordlist.files == result['file_paths']
assert wordlist.words == result['words']