SQLMAP 源码分析 Part1:流程篇
0x00 概述
1.drops 之前的文档 SQLMAP 进阶使用介绍过 SQLMAP 的高级使用方法,
网上也有几篇介绍过 SQLMAP 源码的文章曾是土木人,都写的非常好,
建议大家都看一下。
2.我准备分几篇文章详细的介绍下 SQLMAP 的源码,让想了解的朋友
们熟悉一下 SQLMAP 的原理和一些手工注入的语句,今天先开始第一
篇:流程篇。
3.之前最好了解 SQMAP 各个选项的意思,可以参考 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 行:
1
2
paths.SQLMAP_ROOT_PATH = modulePath()
setPaths()
进入 common.py 中的 setPaths()函数后,就可以看到这个函数是定义 SQLMAP 路
径和文件的,类似于:
1
2
3
4
5
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,这两个是随机字符串,后面会有介绍。
1
2
3
_setConfAttributes()
_setKnowledgeBaseAttributes()
_mergeOptions(inputOptions, overrideOptions)
3.2 start
102 行的 start 函数,算是检测开始的地方.start()函数位于 controller.py 中。
1
2
3
4
5
if conf.direct:
initTargetEnv()
setupTargetEnv()
action()
return True
首先这四句,意思是,如果你使用-d 选项,那么 sqlmap 就会直接进入 action()
函数,连接数据库,语句类似为:
1
2
3
4
5
6
python sqlmap.py -d "mysql://admin:admin@192.168.21.17: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 循环中,可以进入检测环节
1
for targetUrl, targetMethod, targetData, targetCookie, targetHeaders in kb.targets:
此循环先初始化一些一些变量,然后判断之前是否注入过,如果没有注入过,
testSqlInj=True,否则 testSqlInj=false。后面会进行判断是否检测过。
1
def setupTargetEnv():
2
3
4
5
6
7
_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
identifyWaf()
checkWaf()
if conf.identifyWaf:
1
2
3
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.injections) == 1 and kb.injections[0].place is None)) \
and (kb.injection.place is None or kb.injection.parameter is None):
回到 start 函数,385 行会判断是否注入过,如果还没有测试过参数是否可以注
入,则进入 if 语句中。如果之前测试过,则不会进入此语句。