0x00 概述
1.drops之前的文档 SQLMAP进阶使用介绍过SQLMAP的高级使用方法,网上也有几篇介绍过SQLMAP源码的文章曾是土木人,都写的非常好,建议大家都看一下。
2.我准备分几篇文章详细的介绍下SQLMAP的源码,让想了解的朋友们熟悉一下SQLMAP的原理和一些手工注入的语句,今天先开始第一篇:流程篇。
3.之前最好了解SQLMAP各个选项的意思,可以参考sqlmap用户手册和SQLMAP目录doc/README.pdf
4.内容中如有错误或者没有写清楚的地方,欢迎指正交流。有部分内容是参考上面介绍的几篇文章的,在此一并说明,感谢他们。
0x01 流程图
0x02 调试方法
1.我用的IDE是PyCharm。
2.在菜单栏Run->Edit Configurations。点击左侧的“+”,选择Python,Script中选择sqlmap.py的路径,Script parameters中填入注入时的命令,如下图。
3.打开sqlmap.py,开始函数是main函数,在main函数处下断点。
4.右键Debug ‘sqlmap’,然后程序就自动跳到我们下断点的main()函数处,后面可以继续添加断点进行调试。如下图,左边红色的代表跳转到下一个断点处,上面红色的表示跳到下一句代码处
5.另外,如果要在代码中加中文注释,需要在开始处添加以下语句:#coding:utf-8。
0x03 流程
3.1 初始化
我这里用的版本是:1.0-dev-nongit-20150614
miin()函数开始73行:
paths.SQLMAP_ROOT_PATH = modulePath()
setPaths()
进入common.py中的setPaths()函数后,就可以看到这个函数是定义SQLMAP路径和文件的,类似于:
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,这两个是随机字符串,后面会有介绍。
_setConfAttributes()
_setKnowledgeBaseAttributes()
_mergeOptions(inputOptions, overrideOptions)
3.2 start
102行的start函数,算是检测开始的地方.start()函数位于controller.py中。
if conf.direct:
initTargetEnv()
setupTargetEnv()
action()
return True
首先这四句,意思是,如果你使用-d选项,那么sqlmap就会直接进入action()函数,连接数据库,语句类似为:
python sqlmap.py -d "mysql://admin:admin@192.168.21.17
/* <![CDATA[ */!function(){try{var t="currentScript"in document?document.currentScript:function(){for(var t=document.getElementsByTagName("script"),e=t.length;e--;)if(t[e].getAttribute("cf-hash"))return t[e]}();if(t&&t.previousSibling){var e,r,n,i,c=t.previousSibling,a=c.getAttribute("data-cfemail");if(a){for(e="",r=parseInt(a.substr(0,2),16),n=2;a.length-n;n+=2)i=parseInt(a.substr(n,2),16)^r,e+=String.fromCharCode(i);e=document.createTextNode(e),c.parentNode.replaceChild(e,c)}}}catch(u){}}();/* ]]> */: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,这些参数就是我们输入的
接下来从274行的for循环中,可以进入检测环节
for targetUrl, targetMethod, targetData, targetCookie,
targetHeaders in kb.targets:
此循环先初始化一些一些变量,然后判断之前是否注入过,如果没有注入过,testSqlInj=True,否则testSqlInj=false。后面会进行判断是否检测过。
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
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目录下。
当然检测的方法都比较简单,都是查看返回的数据库包种是否包含了某些特征字符。如:
__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.i