Skip to content

Commit 2360e31

Browse files
authored
Merge pull request #32 from codingo/timk-fuzzy-logic
Timk fuzzy logic implementation
2 parents 8d82d4b + 67b525f commit 2360e31

File tree

7 files changed

+53
-13
lines changed

7 files changed

+53
-13
lines changed

README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ A virtual host scanner that can be used with pivot tools, detect catch-all scena
55

66
## Key Benefits
77

8-
* Quickly highlight unqiue content in catch-all scenarios
8+
* Quickly highlight unique content in catch-all scenarios
99
* Locate the outliers in catch-all scenarios where results have dynamic content on the page (such as the time)
1010
* Identify aliases by tweaking the unique depth of matches
1111
* Wordlist supports standard words and a variable to input a base hostname (for e.g. dev.%s from the wordlist would be run as dev.BASE_HOST)
@@ -66,5 +66,5 @@ $ cat bank.htb | VHostScan.py -t 10.10.10.29 -
6666
### STDIN and WordList
6767
You can still specify a wordlist to use along with stdin. In these cases wordlist information will be appended to stdin. For example:
6868
```bash
69-
$ cat vhostname | VhostScan.py -t localhost -w ./wordlists/wordlist.txt -
69+
$ echo -e 'a.example.com\b.example.com' | VhostScan.py -t localhost -w ./wordlists/wordlist.txt -
7070
```

VHostScan.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ def main():
2727
parser.add_argument('--ignore-content-length', dest='ignore_content_length', type=int, help='Ignore content lengths of specificed amount (default 0).', default=0)
2828
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)
2929
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)
30+
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)
3031
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)
3132
parser.add_argument("-oN", dest="output_normal", help="Normal output printed to a file when the -oN option is specified with a filename argument." )
3233
parser.add_argument("-", dest="stdin", action="store_true", help="By passing a blank '-' you tell VHostScan to expect input from stdin (pipe).", default=False)
@@ -77,13 +78,16 @@ def main():
7778
print("[>] Ignoring Content length: %s" % (arguments.ignore_content_length))
7879

7980
scanner = virtual_host_scanner( arguments.target_hosts, arguments.base_host, wordlist, arguments.port, arguments.real_port, arguments.ssl,
80-
arguments.unique_depth, arguments.ignore_http_codes, arguments.ignore_content_length, arguments.add_waf_bypass_headers)
81+
arguments.unique_depth, arguments.ignore_http_codes, arguments.ignore_content_length, arguments.fuzzy_logic, arguments.add_waf_bypass_headers)
8182

8283
scanner.scan()
83-
output = output_helper(scanner)
84+
output = output_helper(scanner, arguments)
8485

8586
print(output.output_normal_likely())
8687

88+
if(arguments.fuzzy_logic):
89+
print(output.output_fuzzy())
90+
8791
if(arguments.output_normal):
8892
output.write_normal(arguments.output_normal)
8993
print("\n[+] Writing normal ouptut to %s" % arguments.output_normal)

lib/core/__version__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@
22
# |V|H|o|s|t|S|c|a|n| Developed by @codingo_ & @__timk
33
# +-+-+-+-+-+-+-+-+-+ https://github.com/codingo/VHostScan
44

5-
__version__ = '0.7'
5+
__version__ = '1.0'
66

lib/core/discovered_host.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@ def __init__(self):
77
self.hostname = ''
88
self.response_code = 0
99
self.hash = ''
10-
self.keys = []
10+
self.keys = []
11+
self.content = b''

lib/core/virtual_host_scanner.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from lib.core.discovered_host import *
66

77

8+
89
class virtual_host_scanner(object):
910
"""Virtual host scanning class
1011
@@ -19,7 +20,7 @@ class virtual_host_scanner(object):
1920
output: folder to write output file to
2021
"""
2122

22-
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, add_waf_bypass_headers=False):
23+
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):
2324
self.target = target
2425
self.base_host = base_host
2526
self.port = int(port)
@@ -29,6 +30,7 @@ def __init__(self, target, base_host, wordlist, port=80, real_port=80, ssl=False
2930
self.wordlist = wordlist
3031
self.unique_depth = unique_depth
3132
self.ssl = ssl
33+
self.fuzzy_logic = fuzzy_logic
3234
self.add_waf_bypass_headers = add_waf_bypass_headers
3335

3436
# this can be made redundant in future with better exceptions
@@ -87,6 +89,7 @@ def scan(self):
8789
host.hostname = hostname
8890
host.response_code = res.status_code
8991
host.hash = page_hash
92+
host.content = res.content
9093

9194
for key, val in res.headers.items():
9295
output += ' {}: {}\n'.format(key, val)
@@ -118,4 +121,4 @@ def likely_matches(self):
118121
segmented_data = dataframe.groupby("val_col").filter(lambda x: len(x) <= self.unique_depth)
119122
matches = ((segmented_data["key_col"].values).tolist())
120123

121-
return matches
124+
return matches

lib/helpers/output_helper.py

Lines changed: 34 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,32 @@
11
from lib.core.discovered_host import *
22
from lib.helpers.file_helper import *
33
import time
4+
from fuzzywuzzy import fuzz
5+
import itertools
6+
import numpy as np
7+
48

59
class output_helper(object):
6-
def __init__(self, scanner):
10+
def __init__(self, scanner, arguments):
711
self.scanner = scanner
12+
self.arguments = arguments
813

914
def write_normal(self, filename):
1015

1116
file = file_helper(filename)
1217

1318
# todo: finish check_directory (needs regex to split out filename)
1419
# file.check_directory(filename)
15-
file.write_file(self.generate_header() + self.output_normal_likely() + self.output_normal_detail())
20+
21+
output = self.generate_header()
22+
output += self.output_normal_likely()
23+
24+
if(self.arguments.fuzzy_logic):
25+
output += self.output_fuzzy()
26+
27+
output += self.output_normal_detail()
28+
29+
file.write_file(output)
1630

1731
def output_normal_likely(self):
1832
uniques = False
@@ -28,15 +42,30 @@ def output_normal_likely(self):
2842
else:
2943
return "\n[!] No matches with a unique count of {} or less.".format(depth)
3044

45+
46+
def output_fuzzy(self):
47+
output = "\n\n[+] Match similarity using fuzzy logic:"
48+
request_hashes = {}
49+
50+
for host in self.scanner.hosts:
51+
request_hashes[host.hash] = host.content
52+
53+
for a, b in itertools.combinations(request_hashes.keys(), 2):
54+
output += "\n\t[>] {} is {}% similar to {}".format(a, fuzz.ratio(request_hashes[a], request_hashes[b]), b)
55+
56+
return output
57+
58+
3159
def output_normal_detail(self):
3260
output = "\n\n[+] Full scan results"
3361

34-
for p in self.scanner.hosts:
35-
output += "\n\n{} (Code: {}) hash: {}".format(str(p.hostname), str(p.response_code), str(p.hash))
36-
for key in p.keys: output += "\n\t{}".format(key)
62+
for host in self.scanner.hosts:
63+
output += "\n\n{} (Code: {}) hash: {}".format(str(host.hostname), str(host.response_code), str(host.hash))
64+
for key in host.keys: output += "\n\t{}".format(key)
3765

3866
return output
3967

68+
4069
def generate_header(self):
4170
output = "VHostScanner Log: {} {}\n".format(time.strftime("%d/%m/%Y"), time.strftime("%H:%M:%S"))
4271
output += "\tTarget: {}\n\tBase Host: {}\n\tPort: {}".format(self.scanner.target, self.scanner.base_host, self.scanner.port)

wordlists/testing.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
lol
2+
intranet.example.com
3+
dev.example.com

0 commit comments

Comments
 (0)