├── .gitattributes ├── .gitignore ├── README.md ├── bcrpscan.py └── py_unittest.py /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ################# 2 | ## Eclipse 3 | ################# 4 | 5 | *.pydevproject 6 | .project 7 | .metadata 8 | bin/ 9 | tmp/ 10 | *.tmp 11 | *.bak 12 | *.swp 13 | *~.nib 14 | local.properties 15 | .classpath 16 | .settings/ 17 | .loadpath 18 | 19 | # External tool builders 20 | .externalToolBuilders/ 21 | 22 | # Locally stored "Eclipse launch configurations" 23 | *.launch 24 | 25 | # CDT-specific 26 | .cproject 27 | 28 | # PDT-specific 29 | .buildpath 30 | 31 | 32 | ################# 33 | ## Visual Studio 34 | ################# 35 | 36 | ## Ignore Visual Studio temporary files, build results, and 37 | ## files generated by popular Visual Studio add-ons. 38 | 39 | # User-specific files 40 | *.suo 41 | *.user 42 | *.sln.docstates 43 | 44 | # Build results 45 | 46 | [Dd]ebug/ 47 | [Rr]elease/ 48 | x64/ 49 | build/ 50 | [Bb]in/ 51 | [Oo]bj/ 52 | 53 | # MSTest test Results 54 | [Tt]est[Rr]esult*/ 55 | [Bb]uild[Ll]og.* 56 | 57 | *_i.c 58 | *_p.c 59 | *.ilk 60 | *.meta 61 | *.obj 62 | *.pch 63 | *.pdb 64 | *.pgc 65 | *.pgd 66 | *.rsp 67 | *.sbr 68 | *.tlb 69 | *.tli 70 | *.tlh 71 | *.tmp 72 | *.tmp_proj 73 | *.log 74 | *.vspscc 75 | *.vssscc 76 | .builds 77 | *.pidb 78 | *.log 79 | *.scc 80 | 81 | # Visual C++ cache files 82 | ipch/ 83 | *.aps 84 | *.ncb 85 | *.opensdf 86 | *.sdf 87 | *.cachefile 88 | 89 | # Visual Studio profiler 90 | *.psess 91 | *.vsp 92 | *.vspx 93 | 94 | # Guidance Automation Toolkit 95 | *.gpState 96 | 97 | # ReSharper is a .NET coding add-in 98 | _ReSharper*/ 99 | *.[Rr]e[Ss]harper 100 | 101 | # TeamCity is a build add-in 102 | _TeamCity* 103 | 104 | # DotCover is a Code Coverage Tool 105 | *.dotCover 106 | 107 | # NCrunch 108 | *.ncrunch* 109 | .*crunch*.local.xml 110 | 111 | # Installshield output folder 112 | [Ee]xpress/ 113 | 114 | # DocProject is a documentation generator add-in 115 | DocProject/buildhelp/ 116 | DocProject/Help/*.HxT 117 | DocProject/Help/*.HxC 118 | DocProject/Help/*.hhc 119 | DocProject/Help/*.hhk 120 | DocProject/Help/*.hhp 121 | DocProject/Help/Html2 122 | DocProject/Help/html 123 | 124 | # Click-Once directory 125 | publish/ 126 | 127 | # Publish Web Output 128 | *.Publish.xml 129 | *.pubxml 130 | 131 | # NuGet Packages Directory 132 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 133 | #packages/ 134 | 135 | # Windows Azure Build Output 136 | csx 137 | *.build.csdef 138 | 139 | # Windows Store app package directory 140 | AppPackages/ 141 | 142 | # Others 143 | sql/ 144 | *.Cache 145 | ClientBin/ 146 | [Ss]tyle[Cc]op.* 147 | ~$* 148 | *~ 149 | *.dbmdl 150 | *.[Pp]ublish.xml 151 | *.pfx 152 | *.publishsettings 153 | 154 | # RIA/Silverlight projects 155 | Generated_Code/ 156 | 157 | # Backup & report files from converting an old project file to a newer 158 | # Visual Studio version. Backup files are not needed, because we have git ;-) 159 | _UpgradeReport_Files/ 160 | Backup*/ 161 | UpgradeLog*.XML 162 | UpgradeLog*.htm 163 | 164 | # SQL Server files 165 | App_Data/*.mdf 166 | App_Data/*.ldf 167 | 168 | ############# 169 | ## Windows detritus 170 | ############# 171 | 172 | # Windows image file caches 173 | Thumbs.db 174 | ehthumbs.db 175 | 176 | # Folder config file 177 | Desktop.ini 178 | 179 | # Recycle Bin used on file shares 180 | $RECYCLE.BIN/ 181 | 182 | # Mac crap 183 | .DS_Store 184 | 185 | 186 | ############# 187 | ## Python 188 | ############# 189 | 190 | *.py[co] 191 | 192 | # Packages 193 | *.egg 194 | *.egg-info 195 | dist/ 196 | build/ 197 | eggs/ 198 | parts/ 199 | var/ 200 | sdist/ 201 | develop-eggs/ 202 | .installed.cfg 203 | 204 | # Installer logs 205 | pip-log.txt 206 | 207 | # Unit test / coverage reports 208 | .coverage 209 | .tox 210 | 211 | #Translations 212 | *.mo 213 | 214 | #Mr Developer 215 | .mr.developer.cfg 216 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bcrpscan 2 | ======== 3 | 4 | Base on crawler result web path scanner. 5 | 6 | For a url which is a directory: http://test.com/a/, it will try to get: 7 | 8 | ``` 9 | http://test.com/a.zip 10 | http://test.com/a.rar 11 | http://test.com/a.tar.gz 12 | ... 13 | ``` 14 | 15 | For a url which is a file: http://test.com/b.php, it will try to get: 16 | 17 | ``` 18 | http://test.com/b.php.bak 19 | http://test.com/b.php.1 20 | ... 21 | ``` 22 | 23 | Install 24 | ======== 25 | 26 | - Need: Python2.7 27 | 28 | Usage 29 | ======== 30 | 31 | ``` 32 | bcrpscan.py (-i import_url_list_file | -u url) [-c cookie_file] [-d db_path] [-h] 33 | ``` 34 | 35 | Example 36 | ======== 37 | 38 | ``` 39 | $ python bcrpscan.py -i test_urls 40 | 2014-04-20 19:43:03,484 INFO: http://192.168.1.6/test 41 | 2014-04-20 19:43:13,625 INFO: http://192.168.1.6/test67187c0f 42 | 2014-04-20 19:43:13,632 INFO: http://192.168.1.6/test.tar.gz 43 | 2014-04-20 19:43:13,638 INFO: http://192.168.1.6/test.zip 44 | 2014-04-20 19:43:13,646 INFO: http://192.168.1.6/test.rar 45 | 2014-04-20 19:43:13,733 INFO: http://192.168.1.667187c0f 46 | 2014-04-20 19:43:13,862 INFO: http://192.168.1.6/test.tar.bz2 47 | 2014-04-20 19:43:13,867 INFO: [+] http://192.168.1.6/test.rar 48 | 2014-04-20 19:43:23,847 INFO: http://192.168.1.6/test.rar250 49 | 50 | ------------------------------ 51 | Probed web paths: 52 | http://192.168.1.6/test.rar 53 | ``` 54 | 55 | Copyright (c) 2014 secfree, released under the GPL license 56 | -------------------------------------------------------------------------------- /bcrpscan.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding=utf-8 3 | 4 | """ 5 | Copyright (c) 2014 secfree, released under the GPL license 6 | 7 | Author: secfree 8 | Email: zzd7zzd#gmail.com 9 | """ 10 | 11 | import sys 12 | import getopt 13 | import logging 14 | import sqlite3 15 | import threading 16 | import socket 17 | import hashlib 18 | import StringIO 19 | import urlparse 20 | import time 21 | 22 | import pycurl 23 | 24 | 25 | COOKIE = '' 26 | DIR_PROBE_EXTS = ['.tar.gz', '.zip', '.rar', '.tar.bz2'] 27 | FILE_PROBE_EXTS = ['.bak', '.swp', '.1'] 28 | NOT_EXIST = hashlib.md5("not_exist").hexdigest()[8:16] 29 | 30 | 31 | def usage(): 32 | print """ 33 | Usage: 34 | bcrpscan.py (-i import_url_list_file | -u url) [-c cookie_file] [-d db_path] [-h] 35 | """ 36 | 37 | 38 | def probe_url(url): 39 | """ 40 | Scan web path based on an url. 41 | 42 | :param url: url 43 | :type url: str 44 | """ 45 | if not url or not url.startswith('http'): 46 | return 47 | if url.count('/') == 2: 48 | url = '%s/' % url 49 | if url[-1] != '/': 50 | code, rurl, length = get_url(url) 51 | durl = '%s/' % url 52 | if code/100 == 3 and rurl and rurl.startswith(durl): 53 | url = durl 54 | 55 | pr = urlparse.urlparse(url) 56 | paths = get_parent_paths(pr.path) 57 | for p in paths: 58 | if p[-1] == '/': 59 | for ext in DIR_PROBE_EXTS: 60 | u = '%s://%s%s%s' % (pr.scheme, pr.netloc, p[:-1], ext) 61 | code, rurl, length = get_url(u) 62 | if code == 200: 63 | logging.info("[+] %s" % u) 64 | else: 65 | for ext in FILE_PROBE_EXTS: 66 | u = '%s://%s%s%s' % (pr.scheme, pr.netloc, p, ext) 67 | code, rurl, length = get_url(u) 68 | if code == 200: 69 | logging.info("[+] %s" % u) 70 | 71 | 72 | def get_parent_paths(path): 73 | ''' 74 | Get a path's parent paths. 75 | 76 | :param path: path 77 | :type path: str 78 | 79 | :rparam: parent paths 80 | :rtype: list 81 | ''' 82 | paths = [] 83 | if not path or path[0] != '/': 84 | return paths 85 | paths.append(path) 86 | tph = path 87 | if path[-1] == '/': 88 | tph = path[:-1] 89 | while tph: 90 | tph = tph[:tph.rfind('/')+1] 91 | paths.append(tph) 92 | tph = tph[:-1] 93 | return paths 94 | 95 | 96 | def get_url(url): 97 | ''' 98 | Request a url. 99 | 100 | :param url: url 101 | :type url: str 102 | 103 | :rparams: (code, rurl, length) 104 | :rtypes: (int, str, int) 105 | ''' 106 | logging.info(url) 107 | 108 | # set pycurl request 109 | pcr = pycurl.Curl() 110 | pcr.setopt(pycurl.FOLLOWLOCATION, 0) 111 | pcr.fp = StringIO.StringIO() 112 | pcr.setopt(pcr.WRITEFUNCTION, pcr.fp.write) 113 | pcr.setopt(pycurl.TIMEOUT, 10) 114 | pcr.setopt(pycurl.NOSIGNAL, 1) 115 | try: 116 | pcr.setopt(pycurl.URL, str(url)) 117 | except TypeError as err: 118 | logging.info('type: %s -- err: %s' % (str(type(url)), str(err))) 119 | return (0, '', 0) 120 | 121 | # set cookie 122 | if COOKIE: 123 | pcr.setopt(pycurl.COOKIE, COOKIE) 124 | 125 | # request url 126 | try: 127 | pcr.perform() 128 | except pycurl.error as err: 129 | logging.error('url: %s -- %s' % ( url, str(err))) 130 | if 'transfer closed' not in str(err): 131 | return (0, '', 0) 132 | 133 | code = pcr.getinfo(pycurl.HTTP_CODE) 134 | rurl = pcr.getinfo(pycurl.REDIRECT_URL) 135 | html = pcr.fp.getvalue() 136 | length = len(html) 137 | 138 | # response is null 139 | if (code == 200) and (length == 1) and (ord(html[0]) == 0): 140 | return (0, '', 0) 141 | if (length < 2049) and ('document.location= "http://sh.114so.cn/"' in html): 142 | return (0, '', 0) 143 | 144 | pcr.close() 145 | return (code, rurl, length) 146 | 147 | 148 | def set_log(): 149 | ''' 150 | set log 151 | ''' 152 | format = "%(asctime)s %(levelname)s: %(message)s" 153 | logging.basicConfig(format=format, level=logging.INFO) 154 | 155 | 156 | class Db_path: 157 | ''' 158 | sqlite db operation 159 | ''' 160 | def __init__(self): 161 | self.con = None 162 | self.cur = None 163 | 164 | def connect(self, db_file): 165 | ''' 166 | connect to db_file 167 | ''' 168 | self.con = sqlite3.connect(db_file) 169 | self.con.row_factory = sqlite3.Row 170 | self.cur = self.con.cursor() 171 | 172 | def create(self): 173 | ''' 174 | create table 175 | ''' 176 | sql_create = [ 177 | ''' 178 | create table if not exists webpath ( 179 | id integer primary key autoincrement, 180 | host varchar(1024), 181 | scheme varchar(10), 182 | path varchar(1024), 183 | probed integer 184 | ); 185 | ''', 186 | ''' 187 | create table if not exists vuln ( 188 | id integer primary key autoincrement, 189 | url varchar(1024), 190 | code varchar(10), 191 | length integer 192 | ) 193 | ''' 194 | ] 195 | try: 196 | for sql in sql_create: 197 | self.cur.execute(sql) 198 | self.con.commit() 199 | except sqlite3.Error as err: 200 | logging.error('%s:%s %s', __file__, sys._getframe().f_lineno, str(err)) 201 | return False 202 | return True 203 | 204 | def insert(self, table, st): 205 | ''' 206 | do insert 207 | ''' 208 | keys = {} 209 | keys['webpath'] = ('host', 'scheme', 'path', 'probed') 210 | keys['vuln'] = ('url', 'code', 'length') 211 | values = [] 212 | ikys = '' 213 | n = len(keys[table]) 214 | for i in range(n): 215 | ikys += '%s, ' % keys[table][i] 216 | values.append(st[keys[table][i]]) 217 | ikys = ikys[:-2] 218 | iqs = '?, ' * n 219 | iqs = iqs[:-2] 220 | sql = 'insert into %s(%s) values(%s);' % (table, ikys, iqs) 221 | try: 222 | self.cur.execute(sql, values) 223 | except sqlite3.Error as err: 224 | logging.error('%s:%s %s', __file__, sys._getframe().f_lineno, str(err)) 225 | return False 226 | return True 227 | 228 | def commit(self): 229 | try: 230 | self.con.commit() 231 | except sqlite3.Error as err: 232 | logging.error('%s:%s %s', __file__, sys._getframe().f_lineno, str(err)) 233 | return False 234 | return True 235 | 236 | def update(self, table, conditions=None, **kwargs): 237 | ''' 238 | update table 239 | ''' 240 | if len(kwargs) == 0: 241 | return True 242 | values = [] 243 | sql = 'update %s set ' % table 244 | for k in kwargs: 245 | sql += '%s = ?, ' % k 246 | values.append(kwargs[k]) 247 | sql = sql[:-2] 248 | if conditions and len(conditions) > 0: 249 | sql += ' where' 250 | for k in conditions: 251 | sql += ' %s=? and ' % k 252 | values.append(conditions[k]) 253 | sql = sql[:-5] 254 | sql += ';' 255 | 256 | try: 257 | self.cur.execute(sql, values) 258 | except sqlite3.Error as err: 259 | logging.error('%s:%s %s', __file__, sys._getframe().f_lineno, str(err)) 260 | return False 261 | return True 262 | 263 | def fetch(self, table, num = None, keys=(), **conditions): 264 | ''' 265 | fetch rows from table 266 | ''' 267 | values = [] 268 | sql = 'select ' 269 | if len(keys) > 0: 270 | for k in keys: 271 | sql += '%s, ' % k 272 | sql = sql[:-2] 273 | else: 274 | sql += '*' 275 | sql += ' from %s ' % table 276 | if len(conditions) > 0: 277 | sql += 'where' 278 | for k in conditions: 279 | sql += ' %s=? and ' % k 280 | values.append(conditions[k]) 281 | sql = sql[:-5] 282 | if num: 283 | sql += ' limit ?' 284 | values.append(num) 285 | sql += ';' 286 | 287 | try: 288 | self.cur.execute(sql, values) 289 | rows = self.cur.fetchall() 290 | except sqlite3.Error as err: 291 | logging.error('%s:%s %s', __file__, sys._getframe().f_lineno, str(err)) 292 | return None 293 | 294 | return rows 295 | 296 | def query(self, sql): 297 | ''' 298 | execute a query sql 299 | ''' 300 | try: 301 | self.cur.execute(sql) 302 | rows = self.cur.fetchall() 303 | except sqlite3.Error as err: 304 | logging.error('%s:%s %s', __file__, sys._getframe().f_lineno, str(err)) 305 | return None 306 | 307 | return rows 308 | 309 | def close(self): 310 | self.cur.close() 311 | self.con.close() 312 | 313 | 314 | def add_path(db, pr): 315 | """ 316 | Add url to database. 317 | 318 | :param db: database 319 | :type db: object 320 | 321 | :param pr: url parse result 322 | :type pr: object 323 | """ 324 | paths = get_parent_paths(pr.path) 325 | for p in paths: 326 | rows = db.fetch( 327 | 'webpath', 328 | host = pr.netloc, 329 | scheme = pr.scheme, 330 | path = p 331 | ) 332 | if not rows: 333 | st = {} 334 | st['host'] = pr.netloc 335 | st['scheme'] = pr.scheme 336 | st['path'] = p 337 | st['probed'] = 0 338 | db.insert('webpath', st) 339 | db.commit() 340 | 341 | 342 | def th_get_real_url(url, 343 | real_urls, 344 | bsm, 345 | lock 346 | ): 347 | """ 348 | A thread judge whether the url's path is directory and add it to real_urls. 349 | 350 | :param url: url 351 | :type url: str 352 | 353 | :param real_urls: list of real urls 354 | :type real_urls: list 355 | 356 | :param bsm: BoundedSemaphore object 357 | :type bsm: object 358 | 359 | :param lock: Lock object 360 | :type lock: object 361 | """ 362 | code, rurl, length = get_url(url) 363 | durl = '%s/' % url 364 | if code == 200: 365 | lock.acquire() 366 | real_urls.append(url) 367 | lock.release() 368 | elif code/100 == 3 and rurl and rurl.startswith(durl): 369 | lock.acquire() 370 | real_urls.append(durl) 371 | lock.release() 372 | bsm.release() 373 | 374 | 375 | def import_file(path, db): 376 | """ 377 | Imort urls from a file. 378 | 379 | :param path: path of url file 380 | :type path: str 381 | 382 | :param db: database 383 | :type db: object 384 | """ 385 | bsm = threading.BoundedSemaphore(30) 386 | lock = threading.Lock() 387 | real_urls = [] 388 | ths = [] 389 | 390 | fs = open(path) 391 | for line in fs: 392 | line = line.strip() 393 | if not line.startswith('http'): 394 | continue 395 | if line.count('/') == 2: 396 | line = '%s/' % line 397 | 398 | pr = urlparse.urlparse(line) 399 | rows = db.fetch( 400 | 'webpath', 401 | host = pr.netloc, 402 | scheme = pr.scheme, 403 | path = pr.path 404 | ) 405 | if rows: 406 | continue 407 | if line[-1] != '/': 408 | rows = db.fetch( 409 | 'webpath', 410 | host = pr.netloc, 411 | scheme = pr.scheme, 412 | path = '%s/' % pr.path 413 | ) 414 | if rows: 415 | continue 416 | bsm.acquire() 417 | th = threading.Thread( 418 | target=th_get_real_url, 419 | args=(line, real_urls, bsm, lock) 420 | ) 421 | # The main thread exit, the son should thread exit too. 422 | # Sometimes get_url() may be stocked. 423 | th.setDaemon(True) 424 | th.start() 425 | ths.append(th) 426 | else: 427 | add_path(db, pr) 428 | 429 | # wait until socket timeout 430 | time.sleep(10) 431 | for th in ths: 432 | th.join() 433 | fs.close() 434 | 435 | for u in real_urls: 436 | pr = urlparse.urlparse(u) 437 | add_path(db, pr) 438 | real_urls = [] 439 | 440 | 441 | def th_probe_url( 442 | url, 443 | vulns, 444 | bsm, 445 | lock 446 | ): 447 | """ 448 | A thread probe a url. 449 | 450 | :param url: url 451 | :type url: str 452 | 453 | :param vulns: vulns list 454 | :type vulns: list 455 | 456 | :param bsm: BoundedSemaphore object 457 | :type bsm: object 458 | 459 | :param lock: Lock object 460 | :type lock: object 461 | """ 462 | global COOKIE, DIR_PROBE_EXTS, FILE_PROBE_EXTS, NOT_EXIST 463 | 464 | tmp_vus = [] 465 | if url[-1] == '/': 466 | u = '%s%s' % (url[:-1], NOT_EXIST) 467 | code, rurl, length = get_url(u) 468 | if code != 200: 469 | for ext in DIR_PROBE_EXTS: 470 | u = '%s%s' % (url[:-1], ext) 471 | code, rurl, length = get_url(u) 472 | if code == 200: 473 | tmp_vus.append({'url': u, 'code': code, 'length': length}) 474 | else: 475 | u = '%s%s' % (url, NOT_EXIST) 476 | code, rurl, length = get_url(u) 477 | if code != 200: 478 | for ext in FILE_PROBE_EXTS: 479 | u = '%s%s' % (url, ext) 480 | code, rurl, length = get_url(u) 481 | if code == 200: 482 | tmp_vus.append({'url': u, 'code': code, 'length': length}) 483 | 484 | # Sometimes, when getting NOT_EXIST url, its code is not 200 for network reasons. 485 | # At this time, vuln is not exist. 486 | if len(tmp_vus) == 1: 487 | lock.acquire() 488 | vulns.append(tmp_vus[0]) 489 | lock.release() 490 | logging.info('[+] %s' % tmp_vus[0]['url']) 491 | 492 | bsm.release() 493 | 494 | 495 | def probe(db): 496 | """ 497 | Probe web path. 498 | 499 | :param db: database 500 | :type db: object 501 | """ 502 | lock = threading.Lock() 503 | bsm = threading.BoundedSemaphore(30) 504 | vulns = [] 505 | ths = [] 506 | 507 | while True: 508 | rows = db.fetch('webpath', 1, probed = 0) 509 | if not rows or rows[0]['id'] == None: 510 | break 511 | st = rows[0] 512 | u = '%s://%s%s' % (st['scheme'], st['host'], st['path']) 513 | 514 | bsm.acquire() 515 | th = threading.Thread( 516 | target=th_probe_url, 517 | args=(u, vulns, bsm, lock) 518 | ) 519 | # The main thread exit, the son should thread exit too. 520 | # Sometimes get_url() may be stocked. 521 | th.setDaemon(True) 522 | th.start() 523 | ths.append(th) 524 | 525 | db.update( 526 | 'webpath', 527 | conditions = {'id': st['id']}, 528 | probed = 1, 529 | ) 530 | db.commit() 531 | 532 | time.sleep(10) 533 | for th in ths: 534 | th.join() 535 | 536 | # put vulns into database 537 | check_ext = '250' 538 | for vn in vulns: 539 | # check url once more 540 | code, rurl, length = get_url('%s%s' % (vn['url'], check_ext)) 541 | if code != 200: 542 | db.insert('vuln', vn) 543 | db.commit() 544 | 545 | 546 | def show_vuln(db): 547 | """ 548 | Display vulns in database. 549 | 550 | :param db: database 551 | :type db: object 552 | """ 553 | rows = db.query("select url from vuln order by url;") 554 | if rows: 555 | print '' 556 | print '-' * 30 557 | print 'Probed web paths:' 558 | for r in rows: 559 | print r['url'] 560 | 561 | 562 | if __name__ == "__main__": 563 | if len(sys.argv) == 1: 564 | usage() 565 | sys.exit(-1) 566 | 567 | try: 568 | opts, args = getopt.getopt(sys.argv[1:], "c:d:hi:u:") 569 | except getopt.GetoptError as err: 570 | print >> sys.stderr, str(err) 571 | sys.exit(-1) 572 | 573 | url = '' 574 | url_file = '' 575 | db_path = './db' 576 | for t, a in opts: 577 | if t == '-c': 578 | with open(a) as f: 579 | COOKIE = f.read().strip() 580 | if t == '-d': 581 | db_path = a 582 | if t == '-h': 583 | usage() 584 | sys.exit(0) 585 | if t == '-i': 586 | url_file = a 587 | if t == '-u': 588 | url = '' 589 | 590 | if not url and not url_file: 591 | usage() 592 | sys.exit(-1) 593 | 594 | socket.setdefaulttimeout(10.0) 595 | 596 | if url: 597 | probe_url(url) 598 | 599 | if url_file: 600 | set_log() 601 | 602 | db = Db_path() 603 | db.connect(db_path) 604 | db.create() 605 | 606 | import_file(url_file, db) 607 | 608 | probe(db) 609 | 610 | show_vuln(db) 611 | 612 | db.close() 613 | -------------------------------------------------------------------------------- /py_unittest.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #coding=utf-8 3 | 4 | 5 | """ 6 | Run this script as: 7 | python -m pytest -v py_unittest.py 8 | """ 9 | 10 | 11 | from bcrpscan import test_get_parent_paths 12 | 13 | 14 | def test_get_parent_paths(): 15 | assert set(get_parent_paths('/a/b')) == set(['/', '/a/', '/a/b']) 16 | assert set(get_parent_paths('/a/b//')) == set(['/', '/a/', '/a/b/']) --------------------------------------------------------------------------------