SQLMap原理分析(二)
在SQLMap原理分析(一)通过SQLMap的请求进行了一次简单的分析,大概了解SQLMap一个粗略的运行流程。这次通过源码的Debug进行深入一点研究。
准备
这里使用pycharm进行debug,打开SQLMap项目之后,在【Run】-【Edit Configurations】设置参数:-u http://testasp.vulnweb.com/showforum.asp?id=0 –flush-session。由于上次跑了testasp这个站点有缓存因此加上了–flush-session。
Debug
连接检测
准备工作做好之后,在sqlmapy.py文件第407行下一个断点,然后开启Debug之旅。
可以看到现在已经成功断到407行了,单步步入到main()函数中。发现前面几行代码是获取配置、路径、banner等信息。当运行到136行的时候可以发现已经获取到前面设置的站点参数。
运行到156行步入到init()函数中,2629行至后面可以看到会进行一系列设置:http、threads等等。
步出回到sqlmapy.py中的main函数,继续单步运行会进行一系列判断检查,直到177行会发现start函数,步入到start函数。这时候会来到./lib/core/decorators.py的stackedmethod函数,根据注释,该函数是用来堆栈对齐的回退函数(不太理解啥意思)。看到result关键字直接步入。
发现来到./lib/controller/controller.py的start函数。根据注释可以得知,该函数用来检查url、请求方式、cookie以及是否存在注入等。
经过一系列的信息获取:Method、paramKey、Headers等,来到了420行进行连接等检查。
跟进checkConnection函数,会来到./controller/checks.py。先检查hostname是否是ip形式(xxx.xxx.xxx.xxx),之后检查有没有设置代理。然后输出log信息:尝试连接目标url。
终端上这时候print出该条日志信息。
在1589行可以看到开始进行request请求,跟进Request函数,来到./request/connect.py,通过注释可以得知queryPage函数是用来获取目标url页面内容。
直到1306行调用Connect.getPage发起请求开始获取页面内容。
步入getPage函数,经过一系列的赋值:url、get类型参数、cookie等。
497行调用urllib.request.urlopent发起请求。
515行获取响应正文信息,这跟上篇文章请求第一个请求包相呼应。
570行会关闭连接。
最后getPage函数会return出响应正文、响应头以及响应状态码。继续运行回到queryPage函数,经过一系列处理queryPage函数将响应正文、响应头以及响应状态码也return出去。
之后会来到./lib/core/decorators.py,会将获取到的result返回。
会回到checks.py,最终返回True。这时候SQLMap已经获知目标站点可连接。
WAF判断
之后会开始进行WAF检测&识别。
检测
在423行进行waf检测。
跟进checkWaf函数,又会来到stackedmethod函数,直接到在result进行步入,会来到checkWaf函数。根据注释可以得知sqlmap的waf检测能力来源nmap的http-waf-detect脚本。
首先将几种攻击类型的payload(SQL注入、目录遍历、XSS等)拼接到已有参数发起请求。
如果直接连接错误,可以判断存在WAF。若可正常连接,判断不存在WAF。
识别
继续执行可以看到identifyWaf函数,但由于并未设置检测waf会被判断跳过。可以按住command点击identifyWaf函数跟进查看原理。大概原理是调用waf文件夹下脚本进行检测-得到结果。脚本脚本大概逻辑为:发起请求-获取响应正文、响应头、响应码-根据规则判断-返回结果。
稳定性检测
回到controller运行至436行会有checkStability函数。
跟进该函数,通过备注发现该函数是用来进行稳定性检测。
参数动态检测
经过一系列赋值&判断,运行至535行步入checkDynParam函数,根据注释可以得知该函数是用来检测参数是否为动态。
注入检测
简单判断
运行至558行,终于来到关键的注入检测。跟进heuristicCheckSqlInjection函数,在1023行进行随机字符串获取,随机字符串长度为10。并且该随机字符串需满足单引号或者双引号出现次数为1。
接下来将随机字符串拼接成payload,发起请求。这个请求跟上篇文章请求中的注入判断请求包对应。
1035行调用parseFilePaths函数检测响应正文中是否包含绝对路径。
1036行检测上一个响应是否有数据库错误信息,这时候的上个请求payload是包含单双引号的,通过这种方式可以极快的判断是否存在注入。
由于这里并不会有数据库的报错信息,所以还需要继续运行。在1092行会生成两个随机变量,长度为6。接下来生成带<’">的payload,该payload为第一个随机字符串加上<’">加上第二个随机字符串。
1095行出现agent.payload函数,跟进该函数(./lib/core/agent.py),根据注释得知该函数功能是替换SQL注入参数。
166行调用cleanupPayload函数,根据函数名猜测该函数主要是用来进行payload清理。
最终返回经过处理之后的payload:u'id=__PAYLOAD_DELIMITER__0\'ozeyed<\'">cOuFpj__PAYLOAD_DELIMITER__'
。
之后使用处理之后的payload发起请求。
最后返回kb.heuristicTest。
继续运行回到start函数,570行调用checkSqlInjection开始进行注入检测。
跟进checkSqlInjection函数,调用InjectionDict函数设置注入字典,之后对参数值类型进行检查。
135行调用getSortedInjectionTests函数获取待注入类型及其payload等信息。142行会将tests数据取出来,直至取完才能跳出141行的while循环。
148行由于条件并不满足,会跳过dbms检测。
之后对payload进行处理,直至506、511行调用Request.queryPage请求。这时候的payload包含AND关键字。
之后对结果进行判断,由于并没有满足条件,许多判断都直接pass掉。开始重新构造payload发起请求。直至payload为boolen类型注入,会进入判断设置injectable = True。
拆分一下这里的判断逻辑:
- falsePage与truePage是否相等,falsePage使用的payload为cmpPayload、TruePage使用的payload为reqPayload。假设得到结果为A,A为布尔类型;
- 获取not kb.nullConnection结果,假设得到结果为B,B为布尔类型;
- 判断not(A and B),假设得到结果为C,C为布尔类型;
- 判断trueResult and C,假设得到结果为D,只有trueResult、C同为True。D才能为True,满足条件判断;
- 正常情况下B为True,这时候只有falsePage不等于truePage,才能满足条件。
这时候的判断还是比较简单的判断并不能直接就认为该处存在注入,可以看到终端输出时该处似乎是布尔类型的盲注。
之后对该注入信息进行赋值存储injection变量中。由于这时候injectable为True,所以会跳出372行的for循环。
深度判断
待定。
总结
SQLMap整个运行机制:
- 获取url、thread、headers等信息存储至变量中;
- 网站存活性检测;
- WAF检测&WAF类型识别;
- 稳定性检测;
- 注入检测。
SQLMap关键的脚本:
- ./lib/core/decorators.py
- ./lib/controller/controller.py
- ./request/connect.py
- ./controller/checks.py
- ./lib/core/agent.py
通过SQLMap机制简单分析,后续在手工注入的时候可以参考SQLMap的判断机制。