-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathhttpscan.py
More file actions
executable file
·180 lines (157 loc) · 6.48 KB
/
httpscan.py
File metadata and controls
executable file
·180 lines (157 loc) · 6.48 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
#!/usr/bin/env python
"""
httpscan
Scan networks for HTTP servers
"""
import argparse
import imp
import json
import re
import string
import warnings
from copy import deepcopy
from glob import glob
from os.path import basename, exists
from sys import exit
import requests
from scanner import scan
from logger import log
# To avoid "RuntimeWarning: Parent module 'plugins' not found while handling
# absolute import"
warnings.filterwarnings("ignore")
PORT = 80
#
# Main
#
if __name__ == '__main__':
###########################################################################
# Bootstrap
#
# Parse command line arguments
parser = argparse.ArgumentParser(description='Scan networks for HTTP servers')
parser.add_argument('hosts', help='An IP address for a hostname or network, example: 192.168.1.1 for single host or 192.168.1.1-254 for network.')
parser.add_argument('--fast', help='Change timeout settings for the scanner in order to scan faster (T5).', default=False, action='store_true')
parser.add_argument('--definitions-create', help='Create a definition for a given host', default=False, action='store_true')
parser.add_argument('--port', help='Port to be scanned (default: 80)', type=str, default=PORT)
parser.add_argument('--debug', help='Show additionalinformation in the logs', action='store_true', default=False)
args = parser.parse_args()
# Set debug mode if --debug
if args.debug:
log.level = 10
###########################################################################
# Create new definition from host if "--definitions-create" argument is set
#
if args.definitions_create:
url = 'http://{host}:{port}/'.format(host=args.hosts, port=args.port)
try:
response = requests.get(url, timeout=5, verify=False)
except (requests.exceptions.RequestException, requests.exceptions.SSLError) as e:
log.debug('{url} request error: {exc}'.format(url=url, exc=e))
exit()
valid_charcters = string.ascii_lowercase + string.digits
name = ''.join([char for char in response.headers.get('server', '').lower() if char in valid_charcters])
if not name:
log.debug('Unable to find proper information to create a definition of {url}'.format(url=url))
exit()
definition_path = 'definitions/{}.json'.format(name)
template = json.loads(open('definitions/template.json', 'r').read())
template['name'] = name
template['rules']['headers']['server'] = [response.headers.get('server')]
if exists(definition_path):
log.warning('Definition {name} already exists'.format(name=name))
exit()
# Save definition
f = file(definition_path, 'w')
f.write(json.dumps(template, indent=4))
print template
exit()
###########################################################################
# Scan
#
log.debug('Scanning...')
hosts = scan(args.hosts, args.port, args.fast)
if not hosts:
log.debug('No hosts found with port {port} open.'.format(port=args.port))
exit()
###########################################################################
# Fingerprint
#
# Load definitions DB
definitions_db = {}
for definition_path in glob('definitions/*.json'):
try:
definitions_db[basename(definition_path[:-5])] = json.loads(
open(definition_path).read()
)
except ValueError:
log.warning(
'Unable to load "{path}" due to malformed JSON'.format(
path=definition_path
)
)
# Compile regexp
regexp_header_server = []
for name, definition in definitions_db.iteritems():
for r in definition.get('rules').get('headers').get('server'):
regexp_header_server.append((re.compile(r), name))
regexp_body = []
for name, definition in definitions_db.iteritems():
if definition.get('rules').get('body'):
for r in definition.get('rules').get('body'):
regexp_body.append((re.compile(r), name))
for host, port in hosts:
# Make HTTP request
url = 'http://{host}:{port}/'.format(host=host, port=port)
try:
response = requests.get(url, timeout=5, verify=False)
except (requests.exceptions.RequestException, requests.exceptions.SSLError) as e:
log.debug('{url} request error: {exc}'.format(
url=url,
exc=e
))
continue
identity = None
#
# Analyze response
#
# HTTP server header
header_server = response.headers.get('server')
if header_server:
for regexp, http_server in regexp_header_server:
if regexp.search(header_server):
identity = definitions_db.get(http_server)
break
# Body
body = response.text
if body and not identity:
for regexp, http_server in regexp_body:
if regexp.search(body):
identity = definitions_db.get(http_server)
break
# If identity found, search and run plugins. Default identity otherwise.
if identity:
if identity.get('plugins') and isinstance(identity.get('plugins'), list):
for plugin_name in identity.get('plugins'):
try:
plugin_information = imp.find_module(plugin_name, ['plugins'])
if plugin_information:
plugin = imp.load_module(
'plugins.{name}'.format(name=plugin_name),
*plugin_information
)
identity = plugin.run(host, args.port, deepcopy(identity), response)
except (ImportError, Exception) as e:
log.warning(
'Unable to load plugin "{}" for "{}" definition: {}'.format(
plugin_name, identity.get('name'), e
)
)
else:
identity = {'name': header_server}
log.info('http://{host}:{port}/ {definition_name} | {definition_meta}'.format(
host=host,
port=args.port,
definition_name=identity.get('name'),
definition_meta=identity.get('meta')
)
)