├── 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 | --------------------------------------------------------------------------------