Dowonders'blog

SQLMAP源码阅读

文件整体结构

SQLmap文件结构

  • doc ——readme等描述文档
  • estra—— 拓展程序,包含多种额外功能,声音控制,运行CMD等
  • lib—— sqlmap核心功能实现,如waf检测,注入检测等
  • plugins—— 插件程序,各类数据库的信息和通用操作类
  • procs—— 各类数据库程序,如读写文件操作
  • shell—— 各类一句话木马之类的脚本
  • tamper—— 一些绕过过滤的操作脚本
  • thirdparty—— 第三方库,如beautifulsoup爬虫库等
  • txt—— 字典文件,库名,表明,列名
  • udf—— udf提权
  • waf—— 常见的waf指纹
  • xml—— 数据库指纹以及各类注入检测的攻击载荷

开始阅读

sqlmap使用时,命令行命令是:python sqlmap.py –
那么我们先看一下sqlmap.py这个入口文件。

sqlmap.py

折叠实现细节后结构还是比较清晰的,第一个try那是一些类库的引入,modulePath()和checkEnvironment()则是对路径和运行环境做一些初始化设置和检测,可以不必细说,接下来我们开始看整个代码的入口点main()函数

modulePath()checkEnvironment() 在之前已经提到过其作用,这不是本文重点,setPaths(modulePath())对资源路径的一个初始化;
banner()顾名思义就是打印一些版本信息;
cmdLineOptions.update()以及initOptions()则是对相关输入参数的解析以及初始化,这里涉及到两个很关键的全局变量kb(保存一些操作的结果,比如测试过的参书等)和conf(系统预定义好的参数,用户输入后会保存于此);
那么我们接下来看看cmdlineParse()这个函数他对sqlmap这个工具的所有参数进行了一个定义,比如-u,–data等

在这里sqlmap用到了optparser这个第三方命令行操作类库,OptionGroup方法对各个参数进行了一个分类,个人觉得是一个非常好的习惯,便于以后参数的添加或者修改

接下来就要说到init()这个函数了,主要作用就是根据用户的参数输入对各变量进行一个初始化造作,f12跟到其定义位于lib.core.options.py

如上所述的各类方法 其实根据其命名就可以了解其大致的作用,在这里就说说_purge()这个比较有意思的函数,他是一个删除文件的函数

首先会判断用户是否输入了这个参数操作,如果是则会执行安全删除操作,整个流程大致是遍历文件->修改文件属性->写入随机文件->截断文件->重命名文件/文件夹->最后删除整个文件夹~

进行完init()操作后接下来就是做一个smoketest和livetest主要作用是测试程序本身是否可以跑得通和测试 sqlmap 功能是否完整。

之后就要进入sqlmap十分关键的start()部分了,f12跟进,首先是对一系列输入参数的判断处理

conf.hashFile—— Load and crack hashes from a file (standalone)
conf.direct—— Connection string 直连数据库action()是对数据库进行一系列操作的方法
conf.url—— Target URL (e.g. \”http://www.site.com/vuln.php?id=1\”)
conf.configFile—— Load options from a configuration INI file
总之就是确定操作目标~

进入循环体,首先判断是否需要进行网络连通性检查,如果需要则执行checkInternet()函数,该函数其实就是访问一个固定网址然后进行指纹对比来判断网络连通性,然后设置 conf.url,conf.method,conf.data,conf.cookie 和 headers 等字段 也就是一些对参数的处理。

之后会做一个waf检测,waf检测的原理是通过一个附带很多敏感字符IPS_WAF_CHECK_PAYLOAD = “AND 1=1 UNION ALL SELECT 1,NULL,'<script>alert(\”XSS\”)</script>’,table_name FROM information_schema.tables WHERE 2>1–/**/; EXEC xp_cmdshell(‘cat ../../../etc/passwd’)#” 的请求得到的页面与正常请求的页面进行页面相似度比较得到结果,页面相似度算法的大致原理如下 计算两个字符串的相似度,即匹配字符数除以两个字符串中的字符总数。匹配字符是最长公共子序列中的字符加上递归地匹配最长公共子序列两侧的不匹配区域中的字符。更完美的是,python自带的库difflib/SequenceMatcher实现了该算法。


如上图 计算的结果与系统制定的一个标准值(0.5)进行比较,最后返回结果。如果有waf则会提示使用tamper来尝试绕过waf。

identifyWaf()是用来检测waf种类的,原理就是利用sqlmap自带的waf文件夹里的waf指纹库与响应页面进行比对得到结论。

checkNullConnection() 是来检测应该用何种方式实现nullconnection ,nullConnection是一种不用获取页面内容就可以知道页面大小的方法,这种方法在布尔盲注中有非常好的效果,可以很好的节省带宽。 其原理其实是利用了content-length这个头部,而不返回body。各个服务器实现方式如下:

SERVERMETHODRANGE
IIS 6.0HEAD
APACHEGET/POSTX
IBM HTTP GET/POSTX
WEBSPHERE GET/POSTX

接下来就是做一个页面稳定性检测,用的是一个叫做checkStability()的方法 其作用是判断页面是否是一个动态页面,也就是两次相同请求返回的页面是否一样,在比较完之后,如果存在动态页面,还会提出扩展设置(–string/–regex),也就是让使用者辅助剔除动态内容,以方便后续使用。

checkStability() 中还有一个叫做checkDynamicContent(firstPage, secondPage)的方法通过名称可以知道其作用是进一步寻找出动态内容

首先是对nullconnection做一个判断 如果是则后续的步骤都可以跳过了因为null Connection是不通过页面内容来判断页面相似度的,自然也就无法找出动态页面的部分了;接下来判断有没有页面是空页面,如果有则报无法检测的警告;接下来判断页面是否大于系统规定的最大长度(10*1024*1024)如果是则将ratio置为None;我们在这个函数中发现如果firstPage 和 secondPage的相似度小于 0.98(这个相似度的概念就是前一节介绍的页面相似度算法的概念),则会重试,并且尝试findDynamicContent(firstPage, secondPage)然后细化页面究竟是too dynamic还是heavily dynamic。如果页面是too dynamic则提示启用–text-only 选项:有些时候用户知道真条件下的返回页面与假条件下返回页面是不同位置在哪里可以使用–text-only(HTTP响应体中不同)–titles(HTML的title标签中不同)。如果页面仅仅是显示heavy dynamic 的话,sqlmap 会不断重试直到区分出到底是too dynamic还是普通的可以接受的动态页面(相似度大于 0.98)。对于 too dynamic与可以接受的动态页面(相似度高于 0.98),其实最根本的区别就是在于 PageRatio, 如果多次尝试(超过 conf.retries) 设置的尝试次数,仍然出现了相似度低于 0.98 则会认为这个页面too dynamic。

动态页面相关处理结束之后,下一步就是对待检测的参数进行一系列处理;具体步骤注释也说地相当清晰。
1、Order of testing list 对参数进行排序
2、对各个参数适用的level进行检查-level详细的定义会在后续说到
3、对参数是否可用进行一个过滤操作,具体为:是否已经检测过该参数;是否为随机参数;是否为跳过的参数;剔除防御CSRF参数;根据指定level剔除参数~
4、进行动态参数检查,即参数的值不同,返回页面不同,跟进checkDynParam()函数

动态参数检查,主要是如上红框中的两句代码,原理也就是之前waf检查里提到的页面相似度算法,根据参数位置不同构造对应的payload,然后比对页面相似度来判断该参数是否为动态参数,只有是动态参数对于sql注入的检测才有意义。

参数处理完之后就要进入sqlmap比较核心的部分了heuristicCheckSqlInjection()启发式sqli检测,其作用就是判断该参数是否可以注入。下面就跟进该函数看看

首先对是否是过分动态页面做一个判断,如果是,则因为无法通过页面相似度算法来判断是否能注入而跳过启发式检测; 之后从HEURISTIC_CHECK_ALPHABET中随机抽取10个字符出现构造Payload,当然里面的都不是些普通的字符,而是些特殊字符,当我们进行SQL注入测试的时候会很习惯的在参数后面加个分号啊什么的,又或者是其他一些特殊的字符,出现运气好的话有可能会爆出数据的相关错误信息,而那个时候我们就可以根据所爆出的相关错误信息去猜测当前目标的数据库是什么;用parseFilePaths函数检查网页是否爆出绝对路径 ; 用wasLastResponseDBMSError函数判断response中是否包含了可用于甄别数据库类型数据库的报错信息。最后返回启发式检测结果。

在上面一系列操作之后就要进入sqlmap真正地注入检测函数checkSqlInjection ()了。这个函数可以说是sqlmap中最核心的函数了。在这个函数中,处理了 Payload 的各种细节和测试用例的各种细节。
大致执行步骤分为如下几个大部分:
1、根据已知参数类型筛选 boundary
2、启发式检测数据库类型 heuristicCheckDbms
3、payload 预处理(UNION)
4、过滤与排除不合适的测试用例
5、对筛选出的边界进行遍历与 payload 整合
6、payload 渲染
7、针对四种类型的注入分别进行 response 的响应和处理
8、得出结果,返回结果

接下来讲xml.payload用来生成payload的这个文件夹。通过命名可知注入检测的手段有六种:布尔盲注,报错注入,多语句查询注入, 嵌套查询注入 ,时间盲注,Union注入

下面我们来看一个布尔盲注地payload用例,来说明各标签含义

<test>
        <title>AND boolean-based blind - WHERE or HAVING clause (subquery - comment)</title>
        <stype>1</stype>
        <level>2</level>
        <risk>1</risk>
        <clause>1,8,9</clause>
        <where>1</where>
        <vector>AND [RANDNUM]=(SELECT (CASE WHEN ([INFERENCE]) THEN [RANDNUM] ELSE (SELECT [RANDNUM1] UNION SELECT [RANDNUM2]) END))</vector>
        <request>
            <payload>AND [RANDNUM]=(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM]) THEN [RANDNUM] ELSE (SELECT [RANDNUM1] UNION SELECT [RANDNUM2]) END))</payload>
            <comment>[GENERIC_SQL_COMMENT]</comment>
        </request>
        <response>
            <comparison>AND [RANDNUM]=(SELECT (CASE WHEN ([RANDNUM]=[RANDNUM1]) THEN [RANDNUM] ELSE (SELECT [RANDNUM1] UNION SELECT [RANDNUM2]) END))</comparison>
        </response>
    </test>

<tltle> 这个标签是指需要在日志中打印出来地内容,比如上面的例子就会输出”AND boolean-based blind – WHERE or HAVING clause (subquery – comment)”

<stype> 这个标签是指检测注入地手段之前已经说过总共有六种分别是:
1: Boolean-based blind SQL injection
2: Error-based queries SQL injection
3: Inline queries SQL injection
4: Stacked queries SQL injection
5: Time-based blind SQL injection
6: UNION query SQL injection
上述的例子值为1,表示为这个payload为布尔类型的盲注

<level> 这个标签是指检测注入的等级,在之前描述过滤参数的内容时曾提到这个标签,简言之就是等级越高测试的类型参数(如http头部参数,cookie等),语句越多。
1: Always (<100 requests)
2: Try a bit harder (100-200 requests)
3: Good number of requests (200-500 requests)
4: Extensive test (500-1000 requests)
5: You have plenty of time (>1000 requests)
上述的例子等级为2

<risk> 这个标签是指风险,也就是可能会对目标数据库造成破坏的程度,比如级别高的时候可能会出现update操作。
1: Low risk
2: Medium risk
3: High risk
上述的布尔查询显然风险等级低为1

<clause> 这个标签是指该payload适用哪种类型的 SQL 语句
0: Always
1: WHERE / HAVING
2: GROUP BY
3: ORDER BY
4: LIMIT
5: OFFSET
6: TOP
7: Table name
8: Column name
9: Pre-WHERE (non-query)
上述的例子为1,8,9

<where> 这个标签顾名思义在哪里以何种方式添加我们的payload
1: Append the string to the parameter original value——将字符串附加到参数原始值
2: Replace the parameter original value with a negative random integer value and append our string——将参数原始值替换为负随机整数值并附加字符串
3: Replace the parameter original value with our string——将参数原始值替换为我们的字符串
上述例子中值为1

<vector> 这个标签给出的是payload的一个大致模板结构
上述例子AND [RANDNUM]=(SELECT (CASE WHEN ([INFERENCE]) THEN [RANDNUM] ELSE (SELECT [RANDNUM1] UNION SELECT [RANDNUM2]) END))条件判断型注入

<request> 这个标签是用来注明关于发起请求的设置与配置。在这些配置中,有一些是特有的,但是有一些是必须的,例如 payload 是肯定存在的,但是 comment 是不一定有的,char 和 columns 是只有 UNION 才存在​
<payload>
The payload to test for——实际测试使用的 Payload
<comment>
Comment to append to the payload, before the suffix.
<char> 只有 UNION 注入存在的字段
Character to use to bruteforce number of columns in UNION query SQL injection tests. ​
<columns> 只有 UNION 注入存在的字段
Range of columns to test for in UNION query SQL injection tests.

基本payload是按照如上所述的格式生成的。而不同类型的注入方式的原理基本也是我们平时所了解的那样

2 评论

  1. 写得太好了吧~

发表评论