阅读:3868回复:0
SQLMAP源码分析Part1:流程篇
◆0 概述
1.drops之前的文档 SQLMAP进阶使用介绍过SQLMAP的高级使用方法,网上也有几篇介绍过SQLMAP源码的文章曾是土木人,都写的非常好,建议大家都看一下。 2.我准备分几篇文章详细的介绍下SQLMAP的源码,让想了解的朋友们熟悉一下SQLMAP的原理和一些手工注入的语句,今天先开始第一篇:流程篇。 3.之前最好了解SQMAP各个选项的意思,可以参考sqlmap用户手册和SQLMAP目录doc/README.pdf 4.内容中如有错误或者没有写清楚的地方,欢迎指正交流。有部分内容是参考上面介绍的几篇文章的,在此一并说明,感谢他们。 ◆1 流程图 2015072207363161009300825593a0e1949b3fc890ccd2abebd ◆2 调试方法 1.我用的IDE是PyCharm。 2.在菜单栏Run->Edit Configurations。点击左侧的“+”,选择Python,Script中选择sqlmap.py的路径,Script parameters中填入注入时的命令,如下图。 201507220736319065846759eda341ecc794fea9023ca6767a1 3.打开sqlmap.py,开始函数是main函数,在main函数处下断点。 201507220736324460164236e0f7fae8a3f4f2bc712b69f8a6c 4.右键Debug 'sqlmap',然后程序就自动跳到我们下断点的main()函数处,后面可以继续添加断点进行调试。如下图,左边红色的代表跳转到下一个断点处,上面红色的表示跳到下一句代码处 20150722073632290634b240bb403ff138fa7ef5f18f75e8773 5.另外,如果要在代码中加中文注释,需要在开始处添加以下语句:#coding:utf-8。 ◆3 流程 3.1 初始化 我这里用的版本是:1.0-dev-nongit-20150614 miin()函数开始73行: #!python paths.SQLMAP_ROOT_PATH = modulePath() setPaths() 进入common.py中的setPaths()函数后,就可以看到这个函数是定义SQLMAP路径和文件的,类似于: #!python paths.SQLMAP_EXTRAS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "extra") paths.SQLMAP_PROCS_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "procs") paths.SQLMAP_SHELL_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "shell") paths.SQLMAP_TAMPER_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "tamper") paths.SQLMAP_WAF_PATH = os.path.join(paths.SQLMAP_ROOT_PATH, "waf") 接下来的78行函数initOptions(cmdLineOptions),包含了三个函数,作用如流程图所示,设置conf,KB,参数. conf会保存用户输入的一些参数,比如url,端口 kb会保存注入时的一些参数,其中有两个是比较特殊的kb.chars.start和kb.chars.stop,这两个是随机字符串,后面会有介绍。 #!python _setConfAttributes() _setKnowledgeBaseAttributes() _mergeOptions(inputOptions, overrideOptions) 3.2 start 102行的start函数,算是检测开始的地方.start()函数位于controller.py中。 #!python if conf.direct: initTargetEnv() setupTargetEnv() action() return True 首先这四句,意思是,如果你使用-d选项,那么sqlmap就会直接进入action()函数,连接数据库,语句类似为: #!python python sqlmap.py -d "mysql://admin:[email protected]:3306/testdb" -f --banner --dbs --user #!python if conf.url and not any((conf.forms, conf.crawlDepth)): kb.targets.add((conf.url, conf.method, conf.data, conf.cookie, None)) 上面代码会把url,methos,data,cookie加入到kb.targets,这些参数就是我们输入的 201507220736328802400d7d330240a39f1d98235e50d8d4706 接下来从274行的for循环中,可以进入检测环节 #!python for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets: 此循环先初始化一些一些变量,然后判断之前是否注入过,如果没有注入过,testSqlInj=True,否则testSqlInj=false。后面会进行判断是否检测过。 #!python def setupTargetEnv(): _createTargetDirs() _setRequestParams() _setHashDB() _resumeHashDBValues() _setResultsFile() _setAuthCred() 372行setupTargetEnv()函数中包含了5个函数,这些函数作用是 1.创建输出结果目录 2.解析请求参数 3.设置session信息,就是session.sqlite。 4.恢复session的数据,继续扫描。 5.存储扫描结果。 6.添加认证信息 其中比较重要的就是session.sqlite,这个文件在sqlmap的输出目录中,测试的结果都会保存在这个文件里。 3.2.1 checkWaf #!python checkWaf() if conf.identifyWaf: identifyWaf() 377行checkWaf()是检测是否有WAF,检测方法是NMAP的http-waf-detect.nse,比如页面为index.php?id=1,那现在添加一个随机变量index.php?id=1&aaa=2,设置paoyload类似为AND 1=1 UNION ALL SELECT 1,2,3,table_name FROM information_schema.tables WHERE 2>1-- ../../../etc/passwd,如果没有WAF,页面不会变化,如果有WAF,因为payload中有很多敏感字符,大多数时候页面都会发生改变。 接下来的conf.identifyWaf代表sqlmap的参数--identify-waf,如果指定了此参数,就会进入identifyWaf()函数,主要检测的waf都在sqlmap的waf目录下。 20150722073633758721181a0d3cb99b3114ffc342fbf2309dc 当然检测的方法都比较简单,都是查看返回的数据库包种是否包含了某些特征字符。如: #!python __product__ = "360 Web Application Firewall (360)" def detect(get_page): retval = False for vector in WAF_ATTACK_VECTORS: page, headers, code = get_page(get=vector) retval = re.search(r"wangzhan.360.cn", headers.get("X-Powered-By-360wzb", ""), re.I) is not None if retval: break return retval if (len(kb.injections) == 0 or (len(kb.injections) == 1 and kb.injections[0].place is None)) and (kb.injection.place is None or kb.injection.parameter is None): 回到start函数,385行会判断是否注入过,如果还没有测试过参数是否可以注入,则进入if语句中。如果之前测试过,则不会进入此语句。 #!python for place in parameters: # Test User-Agent and Referer headers only if # --level >= 3 skip = (place == PLACE.USER_AGENT and conf.level < skip |= (place == PLACE.REFERER and conf.level < 3) # Test Host header only if # --level >= 5 skip |= (place == PLACE.HOST and conf.level < 5) # Test Cookie header only if --level >= 2 skip |= (place == PLACE.COOKIE and conf.level < 2) 这中间sqlmap给了我们一些注释,可以看到,level>=3时,会测试user-agent,referer,level>=5时,会测试HOST,level>=2时,会测试cookie。当然最终的测试判断还要在相应的xml中指定,后面会介绍。 #!python check = checkDynParam(place, parameter, value) 480行的checkDynParam()函数会判断参数是否是动态的,比如index.php?id=1,通过更改id的值,如果参数是动态的,页面会不同。 3.2.2 heuristicCheckSqlInjection #!python check = heuristicCheckSqlInjection(place, parameter) 502行有个heuristicCheckSqlInjection()函数,翻译过来是启发性sql注入测试,其实就是先进行一个简单的测试,设置一个payload,然后解析请求结果。 heuristicCheckSqlInjection()在checks.py中,821行开始如下: #!python if conf.prefix or conf.suffix: if conf.prefix: prefix = conf.prefix if conf.suffix: suffix = conf.suffix randStr = "" while ''' not in randStr: randStr = randomStr(length=10, alphabet=HEURISTIC_CHECK_ALPHABET) kb.heuristicMode = True payload = "%s%s%s" % (prefix, randStr, suffix) payload = agent.payload(place, parameter, newValue=payload) page, _ = Request.queryPage(payload, place, content=True, raise404=False) kb.heuristicMode = False parseFilePaths(page) result = wasLastResponseDBMSError() 首先conf.prefix和conf.suffix代表用户指定的前缀和后缀;在while ''' not in randStr中,随机选择'"', ''', ')', '(', ',', '.'中的字符,选10个,并且单引号要在。接下来生成一个payload,类似u'name=PAYLOAD_DELIMITER\__1)."."."'."__PAYLOAD_DELIMITER'。其中PAYLOAD_DELIMITER\__1和__PAYLOAD_DELIMITER是随机字符串。请求网页后,调用parseFilePaths进行解析,查看是否爆出绝对路径,而wasLastResponseDBMSError是判断response中是否包含了数据库的报错信息。 #!python value = "%s%s%s" % (randomStr(), DUMMY_XSS_CHECK_APPENDIX, randomStr()) payload = "%s%s%s" % (prefix, "'%s" % value, suffix) payload = agent.payload(place, parameter, newValue=payload) page, _ = Request.queryPage(payload, place, content=True, raise404=False) paramType = conf.method if conf.method not in (None, HTTPMETHOD.GET, HTTPMETHOD.POST) else place if value in (page or ""): infoMsg = "heuristic (XSS) test shows that %s parameter " % paramType infoMsg += "'%s' might be vulnerable to XSS attacks" % parameter logger.info(infoMsg) kb.heuristicMode = False 上面的代码是从888行开始,DUMMY_XSS_CHECK_APPENDIX = " |
|
|