Skip to content

Commit 5373e3e

Browse files
authored
Merge branch 'master' into command-argument-class
2 parents 0a1dea9 + 7350602 commit 5373e3e

File tree

6 files changed

+55
-28
lines changed

6 files changed

+55
-28
lines changed

README.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ $ pip install -r requirements.txt
3939
| -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). |
4040
| --ignore-http-codes IGNORE_HTTP_CODES | Comma separated list of http codes to ignore with virtual host scans (default 404). |
4141
| --ignore-content-length IGNORE_CONTENT_LENGTH | Ignore content lengths of specificed amount. |
42+
| --first-hit | Return first successful result. Only use in scenarios where you are sure no catch-all is configured (such as a CTF). |
4243
| --unique-depth UNIQUE_DEPTH | Show likely matches of page content that is found x times (default 1). |
4344
| --ssl | If set then connections will be made over HTTPS instead of HTTP. |
4445
| --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). |
@@ -51,6 +52,7 @@ $ pip install -r requirements.txt
5152
| -oJ OUTPUT_JSON | JSON output printed to a file when the -oJ option is specified with a filename argument. |
5253
| - | By passing a blank '-' you tell VHostScan to expect input from stdin (pipe). |
5354

55+
5456
## Usage Examples
5557

5658
_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/)._
@@ -98,4 +100,4 @@ pip install -r test-requirements.txt
98100
pytest
99101
```
100102

101-
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. :)
103+
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. :)

VHostScan.py

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,23 +50,26 @@ def main():
5050

5151
user_agents = []
5252
if arguments.user_agent:
53-
print('[>] User-Agent specified, using it')
53+
print('[>] User-Agent specified, using it.')
5454
user_agents = [arguments.user_agent]
5555
elif arguments.random_agent:
56-
print('[>] Random User-Agent flag set')
56+
print('[>] Random User-Agent flag set.')
5757
user_agents = load_random_user_agents()
5858

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

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

6565
print("[>] Ignoring HTTP codes: %s" % (arguments.ignore_http_codes))
66-
66+
6767
if(arguments.ignore_content_length > 0):
6868
print("[>] Ignoring Content length: %s" % (arguments.ignore_content_length))
6969

70+
if arguments.first_hit:
71+
print("[>] First hit is set.")
72+
7073
if not arguments.no_lookup:
7174
for ip in Resolver().query(arguments.target_hosts, 'A'):
7275
host, aliases, ips = gethostbyaddr(str(ip))

lib/core/__version__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,3 @@
33
# +-+-+-+-+-+-+-+-+-+ https://github.com/codingo/VHostScan
44

55
__version__ = '1.6.1'
6-

lib/core/virtual_host_scanner.py

Lines changed: 43 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,27 @@
66
import pandas as pd
77
import time
88
from lib.core.discovered_host import *
9+
from urllib3.util import ssl_
910

1011
DEFAULT_USER_AGENT = 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
1112

13+
_target_host = None
14+
_ssl_wrap_socket = ssl_.ssl_wrap_socket
15+
def ssl_wrap_socket(sock, keyfile=None, certfile=None, cert_reqs=None,
16+
ca_certs=None, server_hostname=None,
17+
ssl_version=None, ciphers=None, ssl_context=None,
18+
ca_cert_dir=None):
19+
ssl_wrap_socket_(sock, keyfile=keyfile, certfile=certfile, cert_reqs=cert_reqs,
20+
ca_certs=ca_certs, server_hostname=_target_host,
21+
ssl_version=ssl_version, ciphers=ciphers, ssl_context=ssl_context,
22+
ca_cert_dir=ca_cert_dir)
23+
ssl_.ssl_wrap_socket = _ssl_wrap_socket
1224

1325
class virtual_host_scanner(object):
1426
"""Virtual host scanning class
15-
27+
1628
Virtual host scanner has the following properties:
17-
29+
1830
Attributes:
1931
wordlist: location to a wordlist file to use with scans
2032
target: the target for scanning
@@ -38,6 +50,7 @@ def __init__(self, target, wordlist, **kwargs):
3850
self.add_waf_bypass_headers = kwargs.get('add_waf_bypass_headers', False)
3951
self.unique_depth = int(kwargs.get('unique_depth', 1))
4052
self.ignore_http_codes = kwargs.get('ignore_http_codes', '404')
53+
self.first_hit = kwargs.get('first_hit')
4154

4255
# this can be made redundant in future with better exceptions
4356
self.completed_scan=False
@@ -85,6 +98,7 @@ def scan(self):
8598
})
8699

87100
dest_url = '{}://{}:{}/'.format('https' if self.ssl else 'http', self.target, self.port)
101+
_target_host = hostname
88102

89103
try:
90104
res = requests.get(dest_url, headers=headers, verify=False)
@@ -99,26 +113,15 @@ def scan(self):
99113

100114
# hash the page results to aid in identifing unique content
101115
page_hash = hashlib.sha256(res.text.encode('utf-8')).hexdigest()
102-
output = '[#] Found: {} (code: {}, length: {}, hash: {})\n'.format(hostname, res.status_code,
103-
res.headers.get('content-length'), page_hash)
104-
host = discovered_host()
105-
host.hostname = hostname
106-
host.response_code = res.status_code
107-
host.hash = page_hash
108-
host.content = res.content
109-
110-
for key, val in res.headers.items():
111-
output += ' {}: {}\n'.format(key, val)
112-
host.keys.append('{}: {}'.format(key, val))
113-
114-
self.hosts.append(host)
115-
116-
# print current results so feedback remains in "realtime"
117-
print(output)
116+
117+
self.hosts.append(self.create_host(res, hostname, page_hash))
118118

119119
# add url and hash into array for likely matches
120120
self.results.append(hostname + ',' + page_hash)
121-
121+
122+
if len(self.hosts) == 2 and self.first_hit:
123+
break
124+
122125
#rate limit the connection, if the int is 0 it is ignored
123126
time.sleep(self.rate_limit)
124127

@@ -141,3 +144,24 @@ def likely_matches(self):
141144
matches = ((segmented_data["key_col"].values).tolist())
142145

143146
return matches
147+
148+
def create_host(self, response, hostname, page_hash):
149+
"""
150+
Creates a host using the responce and the hash.
151+
Prints current result in real time.
152+
"""
153+
output = '[#] Found: {} (code: {}, length: {}, hash: {})\n'.format(hostname, response.status_code,
154+
response.headers.get('content-length'), page_hash)
155+
host = discovered_host()
156+
host.hostname = hostname
157+
host.response_code = response.status_code
158+
host.hash = page_hash
159+
host.content = response.content
160+
161+
for key, val in response.headers.items():
162+
output += ' {}: {}\n'.format(key, val)
163+
host.keys.append('{}: {}'.format(key, val))
164+
165+
print(output)
166+
167+
return host

lib/helpers/output_helper.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ def output_json(self, filename):
6767
'Hash': host.hash,
6868
'Headers': headers}
6969
output['Result'] = result
70-
file.write_file(json.dumps(output))
70+
file.write_file(json.dumps(output, indent=2))
7171

7272

7373
def output_fuzzy(self):

tests/helpers/test_file_helper.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,4 +39,3 @@ def test_get_combined_word_lists(wordlist):
3939

4040
assert wordlist.files == result['file_paths']
4141
assert wordlist.words == result['words']
42-

0 commit comments

Comments
 (0)