SQLMap 源码阅读

0x01 前言

还是代码功底太差,所以想尝试阅读 sqlmap 源码一下,并且自己用 golang 重构,到后面会进行 ysoserial 的改写;以及 xray 的重构,当然那个应该会很多参考 cel-go 项目

0x02 环境准备

sqlmap 的项目地址:https://github.com/sqlmapproject/sqlmap
用 pycharm 打断点调试,因为 vscode 用来调试比较麻烦。

因为要动调,所以需要一个 sql 注入的靶场,这里直接选用的是 sql-labs,用 docker 起

docker pull acgpiano/sqli-labs
docker run -dt --name sqli-lab -p [PORT]:80 acgpiano/sqli-labs:latest

最后还需要重新配置一下数据库,然后才能以 sqli-labs 为靶场进行测试。

这里也挂一下 sqlmap 对应的一些基础操作 ———— https://www.cnblogs.com/hongfei/p/3872156.html

直接在 pycharm 的 Debug 下进行调试,设置参数如下,开始调试

-u "http://81.68.120.14:3333/Less-1/?id=1" -technique=E --dbs

0x03 sqlmap 源码阅读

在开始之前我们有必要确认一下 sqlmap 运行的流程图,很重要!这样有助于我们进一步分析源码。

1. 初始化

sqlmap.py 的 main 函数下断点,开始调试

在没有对 URL 进行发包/探测的时候 sqlmap 会先对一些环境、依赖、变量来做一些初始化的处理

帮助网安学习,全套资料S信免费领取:
① 网安学习成长路径思维导图
② 60+网安经典常用工具包
③ 100+SRC分析报告
④ 150+网安攻防实战技术电子书
⑤ 最权威CISSP 认证考试指南+题库
⑥ 超1800页CTF实战技巧手册
⑦ 最新网安大厂面试题合集(含答案)
⑧ APP客户端安全检测指南(安卓+IOS)

往下,通过 cmdLineParser() 获取参数,cmdLineParser() 通过 argparse 库进行 CLI 的打印与获取,类似的一个小项目我之前也有接触过 https://github.com/Drun1baby/EasyScan

往下 initOptions(cmdLineOptions) 解析命令行参数

init 函数: 初始化

init() 函数中通过调用各种函数进行参数的设置、payload 的加载等,有兴趣的师傅可以点进去阅读一下。

其中这三个相对比较重要,是用来加载 payload 的 ———— loadBoundaries()loadPayloads()_loadQueries()

loadBoundaries()  // 加载闭合符集合
loadPayloads()    // 加载 payload 集合
_loadQueries()    // 加载查询语句,在检测到注入点之后后续进行数据库库名字段名爆破会用到的语句

下个断先点调试一下 loadBoundaries() 函数

首先,会去加载 paths.BOUNDARIES_XML,也就是 data/xml/boundaries.xml

接着进入解析 XML 文件的部分,跟进 parseXmlNode(root)

最终添加到 conf 对象的 tests 属性里

  • loadPayloads() 函数与 _loadQueries() 函数大体上也是如此,都是做了解析 xml 文件的工作,再将内容保存到 conf 对象的 tests 属性里。像 loadPayloads() 函数,最后在 conf.tests 里面可以很清晰的看到 payloads

此时我们还可以看一下 conf 是什么

conf 属性中主要存储了一些目标的相关信息(hostname、path、请求参数等等)以及一些配置信息,init 加载的 payload、请求头 header、cookie 等

init() 函数执行完毕后,就会来到 start() 函数进行项目的正式运行。

初始化功能点小结

简单概括一下初始化部分的代码做了什么事

  • 获取命令行参数并处理
  • 初始化全局变量 conf 以及 kb
  • 获取并解析几个 xml 文件,完成闭合工作、payloads 加载工作
  • 设置 HTTP 相关配置,如 HTTP Header,UA,Session 等

2. URL 处理

f8 下来,先到的是 threadData = getCurrentThreadData(),继续往下走,到 result = f(*args, **kwargs) 代码块,跟进一下

代码逻辑此时来到了 /lib/controller/controller.py 下,往下走,是不会进到 conf.directconf.hashFile 中的,会直接进入到 kb.targets.add() 的代码逻辑里面。

此处的 kb 变量的作用是共享一些对象,其实本质上是保存了注入时的一些参数。kb.targets 添加了我们输入的参数,如图

往下看,大体上是做了一些类似类似打印日志、赋值、添加 HTTP Header 等工作,这一部分代码我们就不看了,直接看最关键的这一部分代码 parseTargetUrl()

跟进

一开始先进行了这一判断

if re.search(r"://\[.+\]", conf.url) and not socket.has_ipv6

判断 http:// 的开头形式是否正确,以及 socket 是否为 ipv6 协议,如果为 ipv6 协议,那么 sqlmap 并不支持。

接着判断

if not re.search(r"^(http|ws)s?://", conf.url, re.I):

判断是 http 开头还是 https 开头,又或者是否是 ws/wss 开头,如果没有这些开头,则就从端口判断,这里我认为或许可以加上 80 与 8080 端口。

继续往下看,进行了 url 的拆分、host 的拆分,并将这些内容保存到 conf 里面的对应属性,后续也是一些基础的判断与赋值,这里不再赘述。

总而言之是在对 URL 进行剖析与拆解,最后这些东西都是放到 conf 里面的

3. 如果这个网站已经被注入过,生成注入检测的payload

核心代码在 controller.py 的第 434 行,需跟进;此处我们可以设置对 kb.injections 的变量监测。先跟进 setupTargetEnv() 函数

  • setupTargetEnv() 函数调用了如下图所示的七个函数

我们跟进最主要的 _resumeHashDBValues() 函数,首先调用了 hashDBRetrieve() 函数,设置检索

出来,到第 476 行,这一次又调用了 hashDBRetrieve() 函数,传参是 HASHDB_KEYS.KB_INJECTIONS,意思就是以 KB_INJECTIONS 作为 KEY 进行检索。跟进发现函数先将需要注入的 URL 信息放到了 _
这个变量中,并将基础信息用 | 符号隔开。

跟进 retrieve() 函数,这个函数做了生成 payload 的工作,具体是怎么生成的我们继续往下看

第 95 行,这里很重要,执行了 SQL 语句,并通过 Hash 加密,加密方式是 base64Pickle 序列化

最终反序列化解密 Payload,说实话这里没看懂是怎么生成的,看上去仅仅是执行了一个 SQL 语句,后面看其他师傅的文章的时候并没有把这一段单独拉出来说,payloads 其实都放在 xml 当中。

接着再循环一次,生成一个 payload

在生成完所有 payload 之后会先对目标进行一次探测,如果 Connection refused 则返回 False

这里生成的 payload 只是很基础的一部分,并非是

4. WAF 检测

解析完 URL 之后对目标进行探测,往下看,位置是 controller.py 的第 439 行,第 448 行有 checkWaf() 的函数,很明显就是要做 WAF 检测的功能。

先会判断这一目标是否存在 WAF,如果存在 WAF 的话,会进行字符的相关 fuzz,当然此处建议对一个存在 WAF 的目标进行测试。值得注意的是,如果这个目标你已经探测过存在 waf,且已知 waf 归属厂商的情况下,就不会走到 payload 那一段代码逻辑当中去,相关的业务代码在 hashDBRetrieve() 下,此处不再展开,比较容易。

如果存在 WAF,则会生成用于 fuzz 的 payload,这个 payload 是基于这个 NMAP 的 http-waf-detect.nse ———— https://seclists.org/nmap-dev/2011/q2/att-1005/http-waf-detect.nse

设置 payload 类似于 "9283 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')#",如果没有 WAF,页面不会变化,如果有 WAF,因为 payload 中有很多敏感字符,大多数时候页面都会发生改变。

接下来的 conf.identifyWaf 代表 sqlmap 的参数 --identify-waf,如果指定了此参数,就会进入 identifyWaf() 函数,主要检测的 waf 都在 sqlmap 的 waf 目录下。不过新版的 sqlmap 已经将这一参数的功能自动放到里面了,无需再指定参数

  • 这里的 payload 先经过处理后赋值给 value,再将 value 作为参数传入 queryPage() 请求中,跟进

在经过很长一段的数据处理与判断代码后,我们到第 1531 行,如图,跟进;getPage() 函数的作用是获取界面的一些信息,如 url,ua,host 等,通过输出比对 payload,为判断 waf 类型提供信息。

  • 获取基本信息

这些基础信息最后都会保存在 response 系列的 message 当中

getPage() 函数中调用了 processResponse() 函数做响应结果的处理,跟进

往下看,到 401 行开始,后续的代码进行了 Waf 的识别

跟进 identYwaf.non_blind_check(),是通过正则表达式来对页面进行匹配,对应的规则在 thirdparty/identywaf/data.json

同时 sqlmap 不光通过规则库来进行判断,也会通过页面相似度来判断是否存在 waf/ips

如果相似度小于设定的 0.5 那么就判定为有 waf 拦截

WAF 注入总结

总结一下就是两点,一种方法是通过正则匹配的检测,另外一种方法是根据页面相似度来检测,我自己应该很难写出来 waf 检测的东西;届时再做尝试。

5. 注入检测之启发式注入

checkWaf() 函数里面出来,先到第 457 行,检测网站是否稳定(因为有些网站一测试可能就炸了)对应此 info

[INFO] testing if the target URL content is stable

继续往下走到第 471 行,会先判断参数是否可以注入,这里与命令的参数 —— --level 挂钩

在前文环境准备的时候我们采用的方式是报错注入,如果不这么做,直接指定参数 --dbs,无法进入到启发式注入里面。我们接着看代码,往下直到第 581 行,调用的 heuristicCheckSqlInjection() 函数,意思是启发性注入。

  • 启发式注入做了哪些工作

1、数据库版本的识别
2、绝对路径获取
3、XSS 的测试

数据库版本的识别

首先会从 HEURISTIC_CHECK_ALPHABET 中随机抽取10个字符出现构造 Payload,当然里面的都不是些普通的字符,而且些特殊字符,当我们进行 SQL 注入测试的时候会很习惯的在参数后面加个分号啊什么的,又或者是其他一些特殊的字符,出现运气好的话有可能会暴出数据的相关错误信息,而那个时候我们就可以根据所暴出的相关错误信息去猜测当前目标的数据库是什么。

并且最后生成的这个 payload 是能够闭合的

实际找个网站测试,如图,这就是报出的 SQL 数据库错误

判断在 lib/request/connect.py 的 1532 行

接着跟进 processResponse() 函数,这里和 waf 对比用的同一种方式,不再详细说明

其中 processResponse() 会调用到 ./lib/parse/html.py 中的 htmlParser() 函数,这一个函数就是根据不同的数据库指纹去识别当前的数据库究竟是什么。

最终实现这一功能的其实是 HTMLHandler 这个类,errors.xml 文件内容如图

这一配置文件的比较简单,其实也就是一些对应数据库的正则。sqlmap 在解析 errors.xml 的时候,然后根据 regexp 中的正则去匹配当前的页面信息然后去确定当前的数据库。这一步和 WAF 比对类似。

到此 sqlmap 就可以确定数据的版本了,从而选择对应的测试 Payload,后续我们会看到这是根据莫索引将 payloads 排序,然后选取对应数据库信息的 payloads 进行测试。减少 sqlmap 的扫描时间。

  • 最后这个 DBMS 探测对应的是这一段信息

获取绝对路径与 XSS 探测

相比指纹识别,获取绝对路径的功能模块相对简单,利用正则匹配寻找出绝对路径。

XSS 的探测也比较简单,这里就不作代码分析了

6. 注入检测之正式注入

从启发式注入里面出来,到第 592 行,进行正式的注入检测,跟进

到第 130 行,获取所有的 payload,后续会根据数据库的信息构建索引,将符合索引的 payload 拿去攻击

往下走,先判断有没有做数据库信息的获取,如果有则跳过,如果没有就先进行上一步的启发式注入

接着根据通过报错得到的数据库信息建立索引,将对应最有效的 payload 拿出来。这些 payloads 会进行 while 循环

第 370 行,通过 cleanupPayload() 函数对 payload 进行处理,主要功能其实是做了 payload 的标签替换

最后替换过的 payload 长这样

"AND (SELECT 2*(IF((SELECT * FROM (SELECT CONCAT('qbpxq',(SELECT (ELT(9125=9125,1))),'qxkvq','x'))s), 8446744073709551610, 8446744073709551610)))"

在 sqlmap 中将payload 分为了三部分,上面生成的 fstpayload 就是中间那部分

prefix + payload + suffix 

prefix 和 suffix 就是对应的,闭合前面的结合以及注释后面的结构,这两个属性主要是从 boundary 中进行获取的,boundary 就是前面加载的 boundaries.xml 配置文件,用来闭合的,所以这里作为了 prefix 和 suffix

最后的拼接

并分别对 prefix 和 suffix 进行 clean,然后进行组合,组合之后的 payload 就是 reqPayload,然后进行请求

  • 发出请求最终还是通过 request.queryPage() 来实现的

请求完毕的结果经过 queryPage() 函数来获取界面,但是页面结果是由 kb.chars.startkb.chars.stop 包裹着的

当第一次的注入不成功的时候,会不断变更 prefix,suffix,当 prefix 和 suffix 都变更完毕但还是无法注入时,才会变更 payload,取出另一个 payload 出来,直至 injectable 变量为 true,同时 output=1

并且 injectable=true

7. 爆数据库等操作

经过上一步正式注入的判断,得到的 injectable=true 参数,才能进行下一步的爆数据库操作.

爆库阶段主要是先经过四个函数处理数据后,再调用 action() 函数,跟进。

这里已爆库为例,先看 --dbs 参数有关的这一块,核心函数是 getDbs()

先根据后台数据库信息,输出日志

第 133 行,queries 就是存放之前初始化 queries.xml 的变量

首先通过 count(schema_name) 来获取数据库的个数,然后再通过 limit num,1 来依次获取数据库名,从 queries 变量中获取语句之后就会传递到 getValue 函数

跟进,前面做了一些基础的设置和 payload 的处理与赋值,比如第 401 行的 cleanQuery() 函数,将语句转换为大写,这里我就不跟进了。直接看关键语句,第 451 行,errorUse() 函数

errorUse() 中首先通过正则将 payload 中的各个部分都进行了获取 ,保存到了对应的 field 当中,最终经过一系列处理,取出了 payload 中的 schema_name

跳出 getFields() 函数,往下,将 expression 的值经过 replace 操作,赋值给了 countedExpression,最终得到的值是 'SELECT COUNT(schema_name) FROM INFORMATION_SCHEMA.SCHEMATA'

第 337 行,跟进 _oneShotErrorUse() 函数,在这一个函数中,sqlmap 对目标网站发包,使用的 payload 为 countedExpression,目的是探测数据库个数(count)

具体业务发包在这里

最后将结果传入 extractRegexResult() 函数中进行正则提取

多线程的方式进行注入,而 runThreads() 函数调用了 errorThread() 函数,最终的注入业务还是由 errorThread() 函数来完成的

跟进一下 _errorFields() 函数,将每一个表进行 while 循环操作,再通过 limitQuery() 函数设置最后的 Limit 语句

最后成功 --dbs

sqlmap 流程分析结束

0x04 小结

sqlmap 的流程分析需要非常重视这张图,当感觉代码看不下去的时候看一下这张图可以事半功倍。

在审计开始之前也可以看一下 utils 文件夹下的 python 文件,总体来说流程并不难,看正则的时候其实挺吃力的。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/687.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Flutter用700行代码纯手工自定义绘制表格控件KqTable

我们项目中往往需要使用到自定义表格&#xff0c;系统提供的表格控件只支持简单的展示功能&#xff0c;并不能很好的满足我们项目的自定义&#xff0c;然而自定义最大自由度的还是自己绘制&#xff0c;所以我选则了自己从头开始自定义绘制一个表格&#xff0c;绘制中不仅需要考…

linux目录——文件管理

个人简介&#xff1a;云计算网络运维专业人员&#xff0c;了解运维知识&#xff0c;掌握TCP/IP协议&#xff0c;每天分享网络运维知识与技能。座右铭&#xff1a;海不辞水&#xff0c;故能成其大&#xff1b;山不辞石&#xff0c;故能成其高。个人主页&#xff1a;小李会科技的…

【C#】组件化开发,调用dll组件方法

系列文章 C#项目–业务单据号生成器&#xff08;定义规则、自动编号、流水号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 C#项目–开始日期结束日期范围计算&#xff08;上周、本周、明年、前年等&#xff09; 本文链接&…

UE笔记-AI Move To无法正常结束/打断 1

启用Stop on Overlap 会导致AI与目标距离受到碰撞影响&#xff0c;实际效果需按要求处理 当Lock AILogic为True时&#xff0c;Move To的Task无法被黑板装饰器打断 当Use Continuos Goal Tracking为True时&#xff0c;Move To的节点不会根据Acceptance Radius设定而结束&#x…

这两天最好的ChatGPT应用;使用Notion AI提升效率的经验(13);AI编程与程序员的生存 | ShowMeAI日报

&#x1f440;日报合辑 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f916; 硅谷银行风波中&#xff0c;OpenAI 创始人大方帮助硅谷初创公司&#xff1a;钱先拿着用&#xff0c;有了再还 OpenAI 创始人 Sam Altman 的弟弟…

数据库基础语法

sql&#xff08;Structured Query Language 结构化查询语言&#xff09; SQL语法 use DataTableName; 命令用于选择数据库。set names utf8; 命令用于设置使用的字符集。SELECT * FROM Websites; 读取数据表的信息。上面的表包含五条记录&#xff08;每一条对应一个网站信息&…

三天吃透计算机网络面试八股文

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

stm32外设-GPIO

0. 写在最前 本栏目笔记都是基于stm32F10x 1. GPIO基本介绍 GPIO—general purpose intput output 是通用输入输出端口的简称&#xff0c;简单来说就是软件可控制的引脚&#xff0c; STM32芯片的GPIO引脚与外部设备连接起来&#xff0c;从而实现与外部通讯、控制以及数据采集的…

802.1x认证和MAC认证讲解

目录 802.1x基础 EAP&#xff08;Extensible Authentication Protocol&#xff09;可扩展认证协议 EAPoL&#xff08;EAP over LAN&#xff09;局域网可扩展认证协议 802.1x体系架构 受控端口的受控方式 802.1x认证 802.1x认证触发方式 客户端退出认证 802.1x认证方式…

【云原生】Linux进程控制(创建、终止、等待)

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; Linux学习之旅 &#x1f38a;每篇一句&#xff1a; 图片来源 &#x1f383;操作环境&#xff1a; CentOS 7.6 阿里云远程服务器 Good judgment comes from experience, and a lot of that comes from bad jud…

一年经验年初被裁面试1月有余无果,还遭前阿里面试官狂问八股,人麻了

最近接到一粉丝投稿&#xff1a;年初被裁员&#xff0c;在家躺平了6个月&#xff0c;然后想着学习下再去面试&#xff0c;现在面试了1个月有余&#xff0c;无果&#xff0c;天天打游戏到半夜&#xff0c;根本无法静下心来学习。下面是他这些天面试经常会被问到的一些问题&#…

【笔记】效率之门——Python中的函数式编程技巧

文章目录Python函数式编程1. 数据2. 推导式3. 函数式编程3.1. Lambda函数3.2. python内置函数3.3. 高阶函数4. 函数式编程的应用Python函数式编程 我的AI Studio项目&#xff1a;【笔记】LearnDL第三课&#xff1a;Python高级编程——抽象与封装 - 飞桨AI Studio (baidu.com) p…

【CSS】盒子模型内边距 ② ( 内边距复合写法 | 代码示例 )

文章目录一、内边距复合写法1、语法2、代码示例 - 设置 1 个值3、代码示例 - 设置 2 个值4、代码示例 - 设置 3 个值5、代码示例 - 设置 4 个值一、内边距复合写法 1、语法 盒子模型内边距 可以通过 padding-left 左内边距padding-right 右内边距padding-top 上内边距padding-…

【数据结构】第二站:顺序表

目录 一、线性表 二、顺序表 1.顺序表的概念以及结构 2.顺序表的接口实现 3.顺序表完整代码 三、顺序表的经典题目 1.移除元素 2.删除有序数组中的重复项 3.合并两个有序数组 一、线性表 在了解顺序表前&#xff0c;我们得先了解线性表的概念 线性表&#xff08;linear…

网站动态背景 | vanta.js的使用

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录前言一、Vanta.js是什么&#xff1f;二、使用步骤1.引入库在项目中安装 three.js 依赖在项目中安装 Vanta JS 依赖2.代码部分&#xff0c;在具体项目中调用&#xff…

分享几个常用的运维 shell 脚本

今天咸鱼给大家分享几个不错的 Linux 运维脚本&#xff0c;这些脚本中大量使用了 Linux 的文本三剑客&#xff1a; awkgrepsed 建议大家这三个工具都要了解并最好能够较为熟练的使用 根据 PID 显示进程所有信息 根据用户输入的PID&#xff0c;过滤出该PID所有的信息 #! /b…

春分策划×运维老王主讲:CMDB数据运营精准化公开课启动报名啦!

『CMDB数据运营精准化』 公开直播课 要来了&#xff01; &#x1f446;扫描海报二维码&#xff0c;预约直播 CMDB似乎是运维中永恒的老话题。 提到CMDB很多人都是又爱又恨&#xff0c;爱的是它给我们提供了一个美好的未来&#xff0c;有了CMDB我们可以解决诸多运维中的难题。…

常见的Web安全漏洞:SYN攻击/CSRF/XSS

一、SYN攻击&#xff08;属于DOS攻击&#xff09; 什么情况下被动方出现SYN_RCVD状态?(flood攻击服务) 客户伪造 ip 端口&#xff0c; 向服务端发送SYN请求。完成2次握手&#xff0c;第三次服务端 等待客户端ACK确认&#xff0c;但由于客户不存在服务端一直未收到确认&#…

Rockchip RV1126 模型部署(完整部署流程)

文章目录1、芯片简介2、部署流程简述3、开发环境配置&#xff08;RKNN-Toolkit&#xff09;3.1、软件安装测试3.2、示例代码解析4、开发环境配置&#xff08;RKNN-NPU&#xff09;4.1、源码结构4.2、 编译源码4.3、源码解析4.4、芯片端运行5、量化算法解析1、芯片简介 环境概述…

动态矢量瓦片缓存库方案

目录 前言 二、实现步骤 1.将数据写入postgis数据库 2.将矢量瓦片数据写入缓存库 3.瓦片接口实现 4.瓦片局部更新接口实现 总结 前言 矢量瓦片作为webgis目前最优秀的数据格式&#xff0c;其主要特点就是解决了大批量数据在前端渲染时出现加载缓慢、卡顿的问题&#xff0…
最新文章