Skip to content

Commit 00a24b4

Browse files
committed
Merge branch 'master' into timk-fuzzy-logic
2 parents 50c9a22 + c17d043 commit 00a24b4

File tree

8 files changed

+141
-32
lines changed

8 files changed

+141
-32
lines changed

.gitignore

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,4 +104,4 @@ ENV/
104104
*.suo
105105
*.pyproj
106106
*.sln
107-
*.sqlite
107+
*.sqlite

README.md

Lines changed: 21 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
# VHostScan
2-
A virtual host scanner that can pivot over hosts, detect catch-all scenarios, aliases and dynamic default pages.
2+
A virtual host scanner that can pivot over hosts, detect catch-all scenarios, aliases and dynamic default pages. First presented at SecTalks BNE in September 2017 (![slidedeck](https://docs.google.com/presentation/d/1KDY7bnCpCGabJn8UpmHGSb6z_hi_WGf3ETxzykTNjWY/)).
33

44
[![Python 3.2|3.6](https://img.shields.io/badge/python-3.2|3.6-green.svg)](https://www.python.org/) [![License](https://img.shields.io/badge/license-GPL3-_red.svg)](https://www.gnu.org/licenses/gpl-3.0.en.html) [![Twitter](https://img.shields.io/badge/twitter-@____timk-blue.svg)](https://twitter.com/__timk) [![Twitter](https://img.shields.io/badge/twitter-@codingo__-blue.svg)](https://twitter.com/codingo_)
55

@@ -11,8 +11,13 @@ A virtual host scanner that can pivot over hosts, detect catch-all scenarios, al
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)
1212
* Work over HTTP and HTTPS
1313
* Ability to set the real port of the webserver to use in headers when pivoting through ssh/nc
14+
* Add simple response headers to bypass some WAF products
1415

15-
## Usage
16+
## Product Comparisons
17+
18+
![VHOSTScan Feature Map](https://github.com/codingo/codingo.github.io/blob/master/assets/featureMap.PNG)
19+
20+
# Usage
1621

1722
| Argument | Description |
1823
| ------------- |:-------------|
@@ -27,16 +32,27 @@ A virtual host scanner that can pivot over hosts, detect catch-all scenarios, al
2732
| --unique-depth UNIQUE_DEPTH | Show likely matches of page content that is found x times (default 1). |
2833
| --ssl | If set then connections will be made over HTTPS instead of HTTP. |
2934

30-
## Examples
31-
35+
## Usage Examples
36+
### Quick Example
3237
The most straightforward example runs the default wordlist against example.com using the default of port 80:
3338

3439
```bash
3540
$ VHostScan.py -t example.com
3641
```
37-
42+
### Port forwarding
3843
Say you have an SSH port forward listening on port 4444 fowarding traffic to port 80 on example.com's development machine. You could use the following to make VHostScan connect through your SSH tunnel via localhost:4444 but format the header requests to suit connecting straight to port 80:
3944

4045
```bash
4146
$ VHostScan.py -t localhost -b example.com -p 4444 -r 80
4247
```
48+
49+
### STDIN
50+
If you want to pipe information into VHostScan you can use the ```-``` flag:
51+
```bash
52+
$ cat vhostname | VHostScan.py -t localhost -
53+
```
54+
### STDIN and WordList
55+
You can still specify a wordlist to use along with stdin. In these cases wordlist information will be appended to stdin. For example:
56+
```bash
57+
$ cat vhostname | VhostScan.py -t localhost -w ./wordlists/wordlist.txt -
58+
```

VHostScan.py

Lines changed: 37 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@
55
from argparse import ArgumentParser
66
from lib.core.virtual_host_scanner import *
77
from lib.helpers.output_helper import *
8+
from lib.core.__version__ import __version__
89

910

1011
def print_banner():
11-
print("+-+-+-+-+-+-+-+-+-+ v. 0.3")
12+
print("+-+-+-+-+-+-+-+-+-+ v. %s" % __version__)
1213
print("|V|H|o|s|t|S|c|a|n| Developed by @codingo_ & @__timk")
1314
print("+-+-+-+-+-+-+-+-+-+ https://github.com/codingo/VHostScan\n")
1415

@@ -17,7 +18,7 @@ def main():
1718
print_banner()
1819
parser = ArgumentParser()
1920
parser.add_argument("-t", dest="target_hosts", required=True, help="Set a target range of addresses to target. Ex 10.11.1.1-255" )
20-
parser.add_argument("-w", dest="wordlist", required=False, type=str, help="Set the wordlist to use (default ./wordlists/virtual-host-scanning.txt)", default="./wordlists/virtual-host-scanning.txt")
21+
parser.add_argument("-w", dest="wordlist", required=False, type=str, help="Set the wordlist to use (default ./wordlists/virtual-host-scanning.txt)")
2122
parser.add_argument("-b", dest="base_host", required=False, help="Set host to be used during substitution in wordlist (default to TARGET).", default=False)
2223
parser.add_argument("-p", dest="port", required=False, help="Set the port to use (default 80).", default=80)
2324
parser.add_argument("-r", dest="real_port", required=False, help="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).", default=False)
@@ -27,26 +28,50 @@ def main():
2728
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)
2829
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)
2930
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)
31+
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)
3032
parser.add_argument("-oN", dest="output_normal", help="Normal output printed to a file when the -oN option is specified with a filename argument." )
31-
arguments = parser.parse_args()
32-
33-
if not os.path.exists(arguments.wordlist):
34-
print("[!] Wordlist %s doesn't exist, ending scan." % arguments.wordlistt)
35-
sys.exit()
36-
37-
print("[+] Starting virtual host scan for %s using port %s and wordlist %s" % (arguments.target_hosts,
38-
str(arguments.port),
39-
arguments.wordlist))
33+
parser.add_argument("-", dest="stdin", action="store_true", help="By passing a blank '-' you tell VHostScan to expect input from stdin (pipe).", default=False)
34+
35+
arguments = parser.parse_args()
36+
wordlist = list()
37+
38+
if(arguments.stdin and not arguments.wordlist):
39+
input = list(line for line in sys.stdin.read().splitlines())
40+
wordlist.extend(input)
41+
print("[+] Starting virtual host scan for %s using port %s and stdin data" % (arguments.target_hosts,
42+
str(arguments.port)))
43+
elif(arguments.stdin and arguments.wordlist):
44+
if not os.path.exists(arguments.wordlist):
45+
print("[!] Wordlist %s doesn't exist and can't be appended to stdin." % arguments.wordlist)
46+
print("[+] Starting virtual host scan for %s using port %s and stdin data" % (arguments.target_hosts,
47+
str(arguments.port)))
48+
else:
49+
wordlist_file = open(arguments.wordlist).read().splitlines()
50+
wordlist.extend(wordlist_file)
51+
print("[+] Starting virtual host scan for %s using port %s, stdin data, and wordlist %s" % (arguments.target_hosts,
52+
str(arguments.port),
53+
arguments.wordlist))
54+
else:
55+
# if no stdin, or wordlist pass, open default wordlist location
56+
wordlist_file = open("./wordlists/virtual-host-scanning.txt").read().splitlines()
57+
wordlist.extend(wordlist_file)
58+
print("[+] Starting virtual host scan for %s using port %s and wordlist %s" % (arguments.target_hosts,
59+
str(arguments.port),
60+
"./wordlists/virtual-host-scanning.txt"))
4061

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

65+
if(arguments.add_waf_bypass_headers):
66+
print("[>] WAF flag set, sending simple WAF bypass headers")
67+
4468
print("[>] Ignoring HTTP codes: %s" % (arguments.ignore_http_codes))
4569

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

49-
scanner = virtual_host_scanner(arguments.target_hosts, arguments.base_host, arguments.port, arguments.real_port, arguments.ssl, arguments.unique_depth, arguments.ignore_http_codes, arguments.ignore_content_length, arguments.wordlist, arguments.fuzzy_logic)
73+
scanner = virtual_host_scanner( arguments.target_hosts, arguments.base_host, wordlist, arguments.port, arguments.real_port, arguments.ssl,
74+
arguments.unique_depth, arguments.ignore_http_codes, arguments.ignore_content_length, arguments.add_waf_bypass_headers)
5075

5176
scanner.scan()
5277
output = output_helper(scanner)

lib/core/__version__.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# +-+-+-+-+-+-+-+-+-+
2+
# |V|H|o|s|t|S|c|a|n| Developed by @codingo_ & @__timk
3+
# +-+-+-+-+-+-+-+-+-+ https://github.com/codingo/VHostScan
4+
5+
__version__ = '0.7'
6+

lib/core/virtual_host_scanner.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,7 @@ class virtual_host_scanner(object):
1919
output: folder to write output file to
2020
"""
2121

22-
def __init__(self, target, base_host, port=80, real_port=80, ssl=False, unique_depth=1, ignore_http_codes='404', ignore_content_length=0,
23-
wordlist="./wordlists/virtual-host-scanning.txt", fuzzy_logic=False):
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):
2423
self.target = target
2524
self.base_host = base_host
2625
self.port = int(port)
@@ -31,6 +30,7 @@ def __init__(self, target, base_host, port=80, real_port=80, ssl=False, unique_d
3130
self.unique_depth = unique_depth
3231
self.ssl = ssl
3332
self.fuzzy_logic = fuzzy_logic
33+
self.add_waf_bypass_headers = add_waf_bypass_headers
3434

3535
# this can be made redundant in future with better exceptions
3636
self.completed_scan=False
@@ -41,22 +41,31 @@ def __init__(self, target, base_host, port=80, real_port=80, ssl=False, unique_d
4141
# store associated data for discovered hosts in array for oN, oJ, etc'
4242
self.hosts = []
4343

44-
def scan(self):
45-
virtual_host_list = open(self.wordlist).read().splitlines()
4644

45+
def scan(self):
4746
if not self.base_host:
4847
self.base_host = self.target
4948

5049
if not self.real_port:
5150
self.real_port = self.port
5251

53-
for virtual_host in virtual_host_list:
52+
for virtual_host in self.wordlist:
5453
hostname = virtual_host.replace('%s', self.base_host)
5554

56-
headers = {
57-
'Host': hostname if self.real_port == 80 else '{}:{}'.format(hostname, self.real_port),
58-
'Accept': '*/*'
59-
}
55+
if self.add_waf_bypass_headers:
56+
headers = {
57+
'Host': hostname if self.real_port == 80 else '{}:{}'.format(hostname, self.real_port),
58+
'Accept': '*/*',
59+
'X-Originating-IP': '127.0.0.1',
60+
'X-Forwarded-For': '127.0.0.1',
61+
'X-Remote-IP': '127.0.0.1',
62+
'X-Remote-Addr': '127.0.0.1'
63+
}
64+
else:
65+
headers = {
66+
'Host': hostname if self.real_port == 80 else '{}:{}'.format(hostname, self.real_port),
67+
'Accept': '*/*'
68+
}
6069

6170
dest_url = '{}://{}:{}/'.format('https' if self.ssl else 'http', self.target, self.port)
6271

lib/helpers/output_helper.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,18 @@ def write_normal(self, filename):
1515
file.write_file(self.generate_header() + self.output_normal_likely() + self.output_normal_detail())
1616

1717
def output_normal_likely(self):
18-
output = "\n[+] Most likely matches with a unique count of {} or less:".format(str(self.scanner.unique_depth))
19-
for p in self.scanner.likely_matches(): output += "\n\t[>] {}".format(p)
20-
21-
return output
18+
uniques = False
19+
depth = str(self.scanner.unique_depth)
20+
output = "\n[+] Most likely matches with a unique count of {} or less:".format(depth)
21+
22+
for p in self.scanner.likely_matches():
23+
output += "\n\t[>] {}".format(p)
24+
uniques = True
25+
26+
if(uniques):
27+
return output
28+
else:
29+
return "\n[!] No matches with a unique count of {} or less.".format(depth)
2230

2331
def output_normal_detail(self):
2432
output = "\n\n[+] Full scan results"
@@ -35,4 +43,4 @@ def generate_header(self):
3543
output += "\n\tReal Port {}\n\tIgnore HTTP Codes: {}".format(self.scanner.real_port,self.scanner.ignore_http_codes)
3644
output += "\n\tIgnore Content Length: {}\n\tWordlist: {}".format(self.scanner.ignore_content_length, self.scanner.wordlist)
3745
output += "\n\tUnique Depth: {}\n\tSSL: {}\n\t".format(self.scanner.unique_depth, self.scanner.ssl)
38-
return output
46+
return output

wordlists/hackthebox.txt

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
apocalyst.htb
2+
arctic.htb
3+
bank.htb
4+
bastard.htb
5+
beep.htb
6+
blocky.htb
7+
blue.htb
8+
brainfuck.htb
9+
calamity.htb
10+
celestial.htb
11+
charon.htb
12+
cronos.htb
13+
devel.htb
14+
enterprise.htb
15+
europa.htb
16+
grandpa.htb
17+
granny.htb
18+
haircut.htb
19+
holiday.htb
20+
jail.htb
21+
jeeves.htb
22+
joker.htb
23+
kotarak.htb
24+
lame.htb
25+
lazy.htb
26+
legacy.htb
27+
mantis.htb
28+
minion.htb
29+
mirai.htb
30+
nightmare.htb
31+
nineveh.htb
32+
node.htb
33+
october.htb
34+
optimum.htb
35+
popcorn.htb
36+
shocker.htb
37+
shrek.htb
38+
sneaky.htb
39+
solidstate.htb
40+
tally.htb
41+
tenten.htb

wordlists/simple.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
%s
2+
127.0.0.1
3+
www.%s
4+
www

0 commit comments

Comments
 (0)