Skip to content

Commit b7565af

Browse files
authored
Merge pull request #80 from vortexau/wordlist-prefix-suffix
Wordlist prefix suffix
2 parents 00d3b10 + e920824 commit b7565af

8 files changed

+136
-4
lines changed

README.md

+3
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ $ pip install -r requirements.txt
4040
| -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). |
4141
| --ignore-http-codes IGNORE_HTTP_CODES | Comma separated list of http codes to ignore with virtual host scans (default 404). |
4242
| --ignore-content-length IGNORE_CONTENT_LENGTH | Ignore content lengths of specificed amount. |
43+
| --prefix PREFIX | Add a prefix to each item in the wordlist, to add dev-\<word\>, test-\<word\> etc |
44+
| --suffix SUFFIX | Add a suffix to each item in the wordlist, to add \<word\>dev, \<word\>dev |
4345
| --first-hit | Return first successful result. Only use in scenarios where you are sure no catch-all is configured (such as a CTF). |
4446
| --unique-depth UNIQUE_DEPTH | Show likely matches of page content that is found x times (default 1). |
4547
| --ssl | If set then connections will be made over HTTPS instead of HTTP. |
@@ -52,6 +54,7 @@ $ pip install -r requirements.txt
5254
| -oN OUTPUT_NORMAL | Normal output printed to a file when the -oN option is specified with a filename argument. |
5355
| -oG OUTPUT_GREPABLE | Grepable output printed to a file when the -oG is specified with a filename argument. |
5456
| -oJ OUTPUT_JSON | JSON output printed to a file when the -oJ option is specified with a filename argument. |
57+
| -v VERBOSE | Increase the output of the tool to show progress |
5558

5659

5760
## Usage Examples

VHostScan.py

100644100755
+7-1
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ def main():
3333

3434
wordlist_helper = WordList()
3535
wordlist, wordlist_types = wordlist_helper.get_wordlist(
36-
arguments.wordlists)
36+
arguments.wordlists, arguments.prefix, arguments.suffix)
3737

3838
if len(wordlist) == 0:
3939
print("[!] No words found in provided wordlists, unable to scan.")
@@ -82,11 +82,17 @@ def main():
8282
wordlist.append(str(ip))
8383
wordlist.append(host)
8484
wordlist.extend(aliases)
85+
if arguments.verbose:
86+
print("[!] Discovered {host}/{ip}. Adding...".
87+
format(ip=str(ip), host=host))
8588
except (dns.resolver.NXDOMAIN):
8689
print("[!] Couldn't find any records (NXDOMAIN)")
8790
except (dns.resolver.NoAnswer):
8891
print("[!] Couldn't find any records (NoAnswer)")
8992

93+
if arguments.verbose:
94+
print("[>] Scanning with %s items in wordlist" % len(wordlist))
95+
9096
scanner_args = vars(arguments)
9197
scanner_args.update({
9298
'target': arguments.target_hosts,

lib/core/__version__.py

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

5-
__version__ = '1.7.1'
5+
__version__ = '1.8'

lib/core/virtual_host_scanner.py

+4
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ def __init__(self, target, wordlist, **kwargs):
5959
self.unique_depth = int(kwargs.get('unique_depth', 1))
6060
self.ignore_http_codes = kwargs.get('ignore_http_codes', '404')
6161
self.first_hit = kwargs.get('first_hit')
62+
self.verbose = kwargs.get('verbose')
6263

6364
self.ignore_content_length = int(
6465
kwargs.get('ignore_content_length', 0)
@@ -104,6 +105,9 @@ def scan(self):
104105
for virtual_host in self.wordlist:
105106
hostname = virtual_host.replace('%s', self.base_host)
106107

108+
if self.verbose:
109+
print("[*] Scanning {hostname}".format(hostname=hostname))
110+
107111
if self.real_port == 80:
108112
host_header = hostname
109113
else:

lib/helpers/wordlist_helper.py

+45-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,11 @@ def get_stdin_wordlist(self):
1919
return list(line for line in sys.stdin.read().splitlines()) \
2020
if not sys.stdin.isatty() else []
2121

22-
def get_wordlist(self, wordlist_files=None):
22+
def get_wordlist(self,
23+
wordlist_files=None,
24+
wordlist_prefix=False,
25+
wordlist_suffix=False):
26+
2327
default_wordlist_file = DEFAULT_WORDLIST_FILE
2428

2529
stdin_words = self.get_stdin_wordlist()
@@ -29,13 +33,53 @@ def get_wordlist(self, wordlist_files=None):
2933

3034
combined_files = wordlist_files or default_wordlist_file
3135
combined = get_combined_word_lists(combined_files)
36+
3237
if combined:
3338
words_type = 'wordlists: {}'.format(
3439
', '.join(combined['file_paths']))
3540
self.set_words(words_type=words_type, words=combined['words'])
3641

42+
# Apply prefixes
43+
if wordlist_prefix:
44+
prefixed = []
45+
for word in self.wordlist:
46+
if(word == '%s'):
47+
continue
48+
elif(self.valid_ip(word)):
49+
continue
50+
else:
51+
prefixed.append(wordlist_prefix + word)
52+
53+
if len(prefixed) > 0:
54+
self.wordlist = self.wordlist + prefixed
55+
56+
if wordlist_suffix:
57+
suffixed = []
58+
for word in self.wordlist:
59+
if(word == '%s'):
60+
continue
61+
elif(self.valid_ip(word)):
62+
continue
63+
elif(".%s" in word):
64+
split = word.split(".")
65+
suffixed.append(split[0] + wordlist_suffix + ".%s")
66+
else:
67+
suffixed.append(word + wordlist_suffix)
68+
69+
if len(suffixed) > 0:
70+
self.wordlist = self.wordlist + suffixed
71+
3772
return self.wordlist, self.wordlist_types
3873

3974
def set_words(self, words_type, words):
4075
self.wordlist_types.append(words_type)
4176
self.wordlist.extend(words)
77+
78+
def valid_ip(self, address):
79+
try:
80+
host_bytes = address.split('.')
81+
valid = [int(b) for b in host_bytes]
82+
valid = [b for b in valid if b >= 0 and b <= 255]
83+
return len(host_bytes) == 4 and len(valid) == 4
84+
except:
85+
return False

lib/input.py

+15
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,16 @@ def setup_parser():
3434
help='Set the port to use (default 80).'
3535
)
3636

37+
parser.add_argument(
38+
'--prefix', dest='prefix', default=False,
39+
help='Add a prefix to each item in the word list (dev, test etc)'
40+
)
41+
42+
parser.add_argument(
43+
'--suffix', dest='suffix', default=False,
44+
help='Add a suffix to each item in the word list'
45+
)
46+
3747
parser.add_argument(
3848
'-r', dest='real_port', type=int, default=False,
3949
help='The real port of the webserver to use in headers when '
@@ -98,6 +108,11 @@ def setup_parser():
98108
help='If set then simple WAF bypass headers will be sent.'
99109
)
100110

111+
parser.add_argument(
112+
'-v', dest='verbose', action='store_true', default=False,
113+
help='Print verbose output'
114+
)
115+
101116
output = parser.add_mutually_exclusive_group()
102117
output.add_argument(
103118
'-oN', dest='output_normal',

tests/helpers/test_wordlist_helper.py

+52-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import unittest
22
import pytest
3-
from mock import patch
3+
from unittest.mock import patch
44

55
from lib.helpers.wordlist_helper import WordList
66
from lib.helpers.wordlist_helper import DEFAULT_WORDLIST_FILE
@@ -18,6 +18,7 @@ def user_wordlist(request, tmpdir_factory):
1818

1919
@pytest.mark.usefixtures('user_wordlist')
2020
class TestWordList(unittest.TestCase):
21+
2122
def setUp(self):
2223
self.wordlist = WordList()
2324
with open(DEFAULT_WORDLIST_FILE, 'r') as word_file:
@@ -45,3 +46,53 @@ def test_using_default_wordlist(self):
4546
with patch('lib.helpers.wordlist_helper.WordList.get_stdin_wordlist', return_value=stdin_wordlist):
4647
wordlist, wordlist_types = self.wordlist.get_wordlist()
4748
self.assertEqual(wordlist, self.default_wordlist)
49+
50+
def test_ip_using_prefix(self):
51+
stdin_wordlist = ['127.0.0.1']
52+
prefix = 'dev-'
53+
with patch('lib.helpers.wordlist_helper.WordList.get_stdin_wordlist', return_value=stdin_wordlist):
54+
wordlist, wordlist_types = self.wordlist.get_wordlist(None, prefix)
55+
self.assertEqual(wordlist, stdin_wordlist)
56+
57+
def test_ip_using_suffix(self):
58+
stdin_wordlist = ['127.0.0.1']
59+
suffix = 'test'
60+
with patch('lib.helpers.wordlist_helper.WordList.get_stdin_wordlist', return_value=stdin_wordlist):
61+
wordlist, wordlist_types = self.wordlist.get_wordlist(None,None,suffix)
62+
self.assertEqual(wordlist,stdin_wordlist)
63+
64+
def test_word_with_prefix(self):
65+
stdin_wordlist = ['www','www2','www3']
66+
expected_wordlist = stdin_wordlist + ['dev-www','dev-www2','dev-www3']
67+
prefix = 'dev-'
68+
with patch('lib.helpers.wordlist_helper.WordList.get_stdin_wordlist', return_value=stdin_wordlist):
69+
wordlist, wordlist_types = self.wordlist.get_wordlist(None,prefix)
70+
self.assertEqual(wordlist,expected_wordlist)
71+
72+
def test_words_with_suffix(self):
73+
stdin_wordlist = ['www','www2','www3']
74+
expected_wordlist = stdin_wordlist + ['wwwtest','www2test','www3test']
75+
suffix = 'test'
76+
with patch('lib.helpers.wordlist_helper.WordList.get_stdin_wordlist', return_value=stdin_wordlist):
77+
wordlist, wordlist_types = self.wordlist.get_wordlist(None,None,suffix)
78+
self.assertEqual(wordlist, expected_wordlist)
79+
80+
def test_words_with_host_and_prefix(self):
81+
stdin_wordlist = ['www.%s']
82+
expected_wordlist = stdin_wordlist + ['test-www.%s']
83+
prefix = 'test-'
84+
with patch('lib.helpers.wordlist_helper.WordList.get_stdin_wordlist', return_value=stdin_wordlist):
85+
wordlist, wordlist_types = self.wordlist.get_wordlist(None, prefix)
86+
self.assertEqual(wordlist, expected_wordlist)
87+
88+
def test_words_with_host_and_suffix(self):
89+
stdin_wordlist = ['www.%s']
90+
expected_wordlist = stdin_wordlist + ['wwwtest.%s']
91+
suffix = 'test'
92+
with patch('lib.helpers.wordlist_helper.WordList.get_stdin_wordlist', return_value=stdin_wordlist):
93+
wordlist, wordlist_types = self.wordlist.get_wordlist(None,None,suffix)
94+
self.assertEqual(wordlist, expected_wordlist)
95+
96+
97+
98+

tests/test_input.py

+9
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@ def test_parse_arguments_default_value(tmpdir):
3232
'output_json': None,
3333
'output_grepable' : None,
3434
'ssl': False,
35+
'prefix': False,
36+
'suffix': False,
37+
'verbose': False
3538
}
3639

3740
assert vars(arguments) == expected_arguments
@@ -59,6 +62,9 @@ def test_parse_arguments_custom_arguments(tmpdir):
5962
'--user-agent', 'some-user-agent',
6063
'--waf',
6164
'-oN', '/tmp/on',
65+
'--prefix','dev-',
66+
'--suffix','test',
67+
'-v'
6268
]
6369

6470
arguments = cli_argument_parser().parse(argv)
@@ -83,6 +89,9 @@ def test_parse_arguments_custom_arguments(tmpdir):
8389
'output_normal': '/tmp/on',
8490
'output_json': None,
8591
'output_grepable' : None,
92+
'prefix': 'dev-',
93+
'suffix': 'test',
94+
'verbose': True
8695
}
8796

8897
assert vars(arguments) == expected_arguments

0 commit comments

Comments
 (0)