├── README.md
└── bannerscan.py
/README.md:
--------------------------------------------------------------------------------
1 | bannerscan
2 | ==========
3 |
4 | C段Banner与路径扫描
5 |
6 | Require:
7 | Python2.6+
8 | requests
9 |
10 | Update[@le4f]:
11 | 原代码上加入部分路径
12 |
13 | more:
14 | http://x0day.me/archives/bannerscan-py.html
15 |
--------------------------------------------------------------------------------
/bannerscan.py:
--------------------------------------------------------------------------------
1 | #coding=utf-8
2 | __author__ = 'DM_'
3 | #Modifed by le4f
4 |
5 | import threading
6 | import requests
7 | import argparse
8 | import time
9 | import re
10 |
11 |
12 | PORTS = (80,
13 | 81,
14 | 443,
15 | 4848,
16 | 8080,
17 | 8090,
18 | 8000,
19 | 8082,
20 | 8888,
21 | 9043,
22 | 8443,
23 | 9200,
24 | 9000,
25 | 9060,
26 | 9440,
27 | 9090,
28 | 8081,
29 | 9043,
30 | 41080,
31 | 9080,
32 | 18100,
33 | 9956,
34 | 8886,
35 | 7778
36 | )
37 |
38 |
39 | PATHS = ('/robots.txt',
40 | '/admin/',
41 | '/manager/html/',
42 | '/jmx-console/',
43 | '/web-console/',
44 | '/jonasAdmin/',
45 | '/manager/',
46 | '/install/',
47 | '/ibm/console/logon.jsp',
48 | '/axis2/axis2-admin/',
49 | '/CFIDE/administrator/index.cfm',
50 | '/FCKeditor/',
51 | '/fckeditor/',
52 | '/fck/',
53 | '/FCK/',
54 | '/HFM/',
55 | '/WEB-INF/',
56 | '/ckeditor/',
57 | '/console/',
58 | '/phpMyAdmin/',
59 | '/Struts2/index.action',
60 | '/index.action',
61 | '/phpinfo.php',
62 | '/info.php',
63 | '/1.php',
64 | '/CHANGELOG.txt',
65 | '/LICENSE.txt',
66 | '/readme.html',
67 | '/cgi-bin/',
68 | '/invoker/',
69 | '/.svn/',
70 | '/test/',
71 | '/CFIDE/',
72 | '/.htaccess',
73 | '/.git/'
74 | )
75 |
76 | HTML_LOG_TEMPLATE="""
77 |
78 |
79 |
80 |
81 | Bannerscan Report
82 |
85 |
86 |
87 | %s
88 |
89 | %s
90 |
91 |
92 |
93 | """
94 | css = """
95 | body{background-color:#FFF;color:#444;font-family:"Droid Serif",Georgia,"Times New Roman",STHeiti,serif;font-size:100%;}
96 | a{color:#3354AA;text-decoration:none;}
97 | a:hover,a:active{color:#444;}
98 | pre,code{background:#F3F3F0;font-family:Menlo,Monaco,Consolas,"Lucida Console","Courier New",monospace;font-size:.92857em;padding:2px 4px;}
99 | code{color:#B94A48;}
100 | pre{overflow:auto;max-height:400px;padding:8px;}
101 | pre code{color:#444;padding:0;}
102 | h1,h2,h3{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;}
103 | textarea{resize:vertical;}.report-meta a,.report-content a,.widget a,a{border-bottom:1px solid#EEE;}.report-meta a:hover,.report-content a:hover,.widget a:hover,a{border-bottom-color:transparent;}#header{padding-top:35px;border-bottom:1px solid#EEE;}#logo{color:#333;font-size:2.5em;}.description{color:#999;font-style:italic;margin:.5em 0 0;}.report{border-bottom:1px solid#EEE;padding:15px 0 20px;}.report-title{font-size:1.4em;margin:.83em 0;}.report-meta{margin-top:-.5em;color:#999;font-size:.92857em;padding:0;}.report-meta li{display:inline-block;padding-left:12px;border-left:1px solid#EEE;margin:0 8px 0 0;}.report-meta li:first-child{margin-left:0;padding-left:0;border:none;}.report-content{line-height:1.5;}.report-content hr,hr{margin:2em auto;width:100px;border:1px solid#E9E9E9;border-width:2px 0 0 0;}
104 | """
105 |
106 | ipPattern = "^([1]?\d\d?|2[0-4]\d|25[0-5])\." \
107 | "([1]?\d\d?|2[0-4]\d|25[0-5])\." \
108 | "([1]?\d\d?|2[0-4]\d|25[0-5])\." \
109 | "([1]?\d\d?|2[0-4]\d|25[0-5])$"
110 |
111 | iprangePattern = "^([1]?\d\d?|2[0-4]\d|25[0-5])\." \
112 | "([1]?\d\d?|2[0-4]\d|25[0-5])\." \
113 | "([1]?\d\d?|2[0-4]\d|25[0-5])\." \
114 | "([1]?\d\d?|2[0-4]\d|25[0-5])-([1]?\d\d?|2[0-4]\d|25[0-5])$"
115 |
116 | ua = "Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6"
117 |
118 | headers = dict()
119 | result = dict()
120 |
121 |
122 | class bannerscan(threading.Thread):
123 | def __init__(self, ip, timeout, headers):
124 | self.ip = ip
125 | self.req = requests
126 | self.timeout = timeout
127 | self.headers = headers
128 | self.per = 0
129 | threading.Thread.__init__(self)
130 |
131 | def run(self):
132 | result[self.ip] = dict()
133 | for port in PORTS:
134 | url_pre = "https://" if port == 443 else "http://"
135 | site = url_pre + self.ip + ":" + str(port)
136 | try:
137 | print ("[*] %s\r" % (site[0:60].ljust(60, " "))),
138 | resp = requests.head(site,
139 | allow_redirects = False,
140 | timeout=self.timeout,
141 | headers=self.headers
142 | )
143 | result[self.ip][port] = dict()
144 |
145 | except Exception, e:
146 | pass
147 |
148 | else:
149 | result[self.ip][port]["headers"] = resp.headers
150 | result[self.ip][port]["available"] = list()
151 |
152 | for path in PATHS:
153 | try:
154 | url = site + path
155 | print ("[*] %s\r" % (url[0:60].ljust(60, " "))),
156 | resp = self.req.get(url,
157 | allow_redirects = False,
158 | timeout=self.timeout,
159 | headers=self.headers
160 | )
161 |
162 | except Exception, e:
163 | pass
164 | else:
165 | if resp.status_code in [200, 406, 401, 403, 500]:
166 | r = re.findall("([\s\S]+?)", resp.content)
167 | title = lambda r : r and r[0] or ""
168 | result[self.ip][port]["available"].append((title(r), url, resp.status_code))
169 |
170 | def getiplst(host, start=1, end=255):
171 | iplst = []
172 | ip_pre = ""
173 | for pre in host.split('.')[0:3]:
174 | ip_pre = ip_pre + pre + '.'
175 | for i in range(start, end):
176 | iplst.append(ip_pre + str(i))
177 | return iplst
178 |
179 | def retiplst(ip):
180 | iplst = []
181 | if ip:
182 | if re.match(ipPattern, ip):
183 | print "[*] job: %s \r" % ip
184 | iplst = getiplst(ip)
185 | return iplst
186 | else:
187 | print "[!] not a valid ip given."
188 | exit()
189 |
190 | def retiprangelst(iprange):
191 | iplst = []
192 | if re.match(iprangePattern, iprange):
193 | ips = re.findall(iprangePattern, iprange)[0]
194 | ip = ips[0] + "." + ips[1] + "." + ips[2] + "." + "1"
195 | ipstart = int(ips[3])
196 | ipend = int(ips[4]) + 1
197 | print "[*] job: %s.%s - %s" % (ips[0] + "." + ips[1] + "." + ips[2], ipstart, ipend)
198 | iplst = getiplst(ip, ipstart, ipend)
199 | return iplst
200 | else:
201 | print "[!] not a valid ip range given."
202 | exit()
203 |
204 | def ip2int(s):
205 | l = [int(i) for i in s.split('.')]
206 | return (l[0] << 24) | (l[1] << 16) | (l[2] << 8) | l[3]
207 |
208 | def log(out, path):
209 | logcnt = ""
210 | centerhtml = lambda ips: len(ips)>1 and str(ips[0]) + " - " + str(ips[-1]) or str(ips[0])
211 | titlehtml = lambda x : x and "" + str(x) + "
" or ""
212 | ips = out.keys()
213 | ips.sort(lambda x, y: cmp(ip2int(x), ip2int(y)))
214 | for ip in ips:
215 | titled = False
216 | if type(out[ip]) == type(dict()):
217 | for port in out[ip].keys():
218 | if not titled:
219 | if len(out[ip][port]['headers']):
220 | logcnt += "%s
" % ip
221 | logcnt += "
"
222 | titled = True
223 | logcnt += "PORT: %s
" % port
224 | logcnt += "Response Headers:"
225 | for key in out[ip][port]["headers"].keys():
226 | logcnt += key + ":" + out[ip][port]["headers"][key] + "\n"
227 | logcnt += "
"
228 | for title, url, status_code in out[ip][port]["available"]:
229 | logcnt += titlehtml(title) + \
230 | "" + url + " "+ \
231 | "Status Code:" + str(status_code) + "
"
232 | logcnt += "
"
233 | center = centerhtml(ips)
234 | logcnt = HTML_LOG_TEMPLATE % ( css, center, logcnt)
235 | outfile = open(path, "a")
236 | outfile.write(logcnt)
237 | outfile.close()
238 |
239 | def scan(iplst, timeout, headers, savepath):
240 | global result
241 | start = time.time()
242 | threads = []
243 |
244 | for ip in iplst:
245 | t = bannerscan(ip,timeout,headers)
246 | threads.append(t)
247 |
248 | for t in threads:
249 | t.start()
250 |
251 | for t in threads:
252 | t.join()
253 |
254 | log(result, savepath)
255 | result = dict()
256 | print
257 |
258 | def main():
259 | parser = argparse.ArgumentParser(description='banner scanner. by DM_ http://x0day.me')
260 | group = parser.add_mutually_exclusive_group()
261 |
262 | group.add_argument('-i',
263 | action="store",
264 | dest="ip",
265 | )
266 | group.add_argument('-r',
267 | action="store",
268 | dest="iprange",
269 | type=str,
270 | )
271 | group.add_argument('-f',
272 | action="store",
273 | dest="ipfile",
274 | type=argparse.FileType('r')
275 | )
276 | parser.add_argument('-s',
277 | action="store",
278 | required=True,
279 | dest="savepath",
280 | type=str,
281 | )
282 | parser.add_argument('-t',
283 | action="store",
284 | required=False,
285 | type = int,
286 | dest="timeout",
287 | default=5
288 | )
289 |
290 | args = parser.parse_args()
291 | savepath = args.savepath
292 | timeout = args.timeout
293 | iprange = args.iprange
294 | ipfile = args.ipfile
295 | ip = args.ip
296 |
297 | headers['user-agent'] = ua
298 |
299 | print "[*] starting at %s" % time.ctime()
300 |
301 | if ip:
302 | iplst = retiplst(ip)
303 | scan(iplst, timeout, headers, savepath)
304 |
305 | elif iprange:
306 | iplst = retiprangelst(iprange)
307 | scan(iplst, timeout, headers, savepath)
308 |
309 | elif ipfile:
310 | lines = ipfile.readlines()
311 | for line in lines:
312 | if re.match(ipPattern, line):
313 | iplst = retiplst(line)
314 | scan(iplst, timeout, headers, savepath)
315 | elif re.match(iprangePattern, line):
316 | iplst = retiprangelst(line)
317 | scan(iplst, timeout, headers, savepath)
318 |
319 | else:
320 | parser.print_help()
321 | exit()
322 |
323 | if __name__ == '__main__':
324 | main()
325 |
326 |
--------------------------------------------------------------------------------