ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

sqlmap实战指南:从参数解析到批量扫描与踩坑排查

sqlmap实战指南:从参数解析到批量扫描与踩坑排查 去年接了一个授权测试项目目标站点的登录接口存在明显的参数拼接痕迹手动用Burp试了试报错注入很快就能确认漏洞存在但真要拿数据、扩展测试其它接口时我立刻意识到一个问题手工会慢很多而且容易遗漏。当时我直接把请求包导进sqlmap几行命令下去库名、表名、字段名、数据一条龙全出来了。从那之后sqlmap就成了我SQL注入测试流程里的标配工具。这篇文章不是从安装开始念手册我会直接按实战使用的思路把sqlmap的常用参数、测试流程、批量扫描方案和个人踩过的坑串起来讲。适合两类人看一类是刚接触WEB安全、想系统学习sqlmap的新手另一类是已经能手工注入、但想提效、想把手动测试过程半自动化甚至自动化的进阶测试人员。读完你至少能掌握怎么针对不同场景选择参数组合、怎么设计一个可靠不跑飞的批量扫描方案、以及遇到误报、漏报、绕不过WAF时该怎么排查。1. sqlmap到底帮你干了什么活想用好一个工具先得知道它的边界在哪。sqlmap本质是一个自动化的SQL注入检测与利用框架它做的事情可以拆成四层。第一层是探测层。你给它一个带参数的URL它会先发一堆原始请求分析响应差异判断哪些参数可能存在注入点。这个阶段靠的是基于布尔的逻辑判断、基于报错信息的关键字匹配、基于时间延迟的响应对比以及联合查询的回显特征。很多人以为sqlmap只是拿现成payload去撞实际上它早就不只是简单payload匹配了它会根据目标数据库的指纹动态调整策略。第二层是利用层。确认存在注入后sqlmap会继续判断注入类型包括布尔盲注、报错注入、联合查询注入、堆叠查询注入、时间盲注和内联查询。它会优先选择效率高、回显清晰的注入方式比如联合查询永远比时间盲注快一个量级。第三层是数据获取层。通过注入点去读取库名、表名、字段名、行数据甚至能直接读写目标服务器文件系统、执行系统命令。这部分能力取决于目标数据库权限和中间件环境不是所有注入点都能走到这一步。第四层是绕过层。针对常见的WAF、输入过滤sqlmap提供了大量tamper脚本能把payload做各种变形比如空格换成注释符、关键字拆开再拼接、大小写混淆、编码转换用来规避过滤规则和WAF检测。那什么场景不适合用sqlmap比如注入点藏在加密参数里或者业务接口有复杂的签名校验sqlmap这种基于原始HTTP请求的工具就插不上手了你得写脚本在加密前hook进去。再比如测试的是API网关后面的接口请求会经过多层动态鉴权直接拿sqlmap跑大概率得到一堆403或302重定向。这些场景需要你先把请求上下文处理干净后面我会详细讲。2. 从安装到第一次跑通注入点2.1 安装别在依赖上浪费时间sqlmap是Python写的跨平台支持做得很好。官方推荐的安装方式是从GitHub仓库拉代码保证版本最新毕竟SQL注入技术日新月异老版本对新型WAF的绕过能力会弱很多。Linux/macOS下习惯用git方式git clone --depth 1 https://github.com/sqlmapproject/sqlmap.git cd sqlmap python sqlmap.py --versionWindows下同样可以下载zip包解压使用。需要注意的是sqlmap需要Python环境2.x版本对应Python 23.x版本对应Python 3新版本推荐直接用Python 3.6以上环境。Kali Linux默认就集成了sqlmap直接用sqlmap命令即可。如果安装后运行报缺少第三方库的错误多半是pygments、prettytable这类依赖没装全。可以手动装一下pip install sqlmap或者直接用系统包管理器比如Kali里的apt install sqlmap把完整依赖一起带上。2.2 第一个注入点从一个需要登录的目标开始实际工作中很少遇到不用登录就能直接测的注入点大部分目标的安全测试都需要你先处理会话状态。这里用一个真实的测试流程举例目标是本地搭建的DVWA靶场。DVWA默认是个登录后才能操作的应用你直接拿登录页面的URL去跑sqlmap大概率什么都测不出来因为未认证请求都被重定向到login.php了。正确做法是先手动登录拿到会话Cookie再把这个Cookie传给sqlmap。打开浏览器开发者工具随便点一个带参数的请求在Network面板里复制Cookie值然后执行python sqlmap.py -u http://127.0.0.1/dvwa/vulnerabilities/sqli/?id1SubmitSubmit --cookiePHPSESSIDyour_session_id; securitylow --batch--cookie参数把会话信息完整传给sqlmap--batch表示所有交互问题都选默认项避免脚本卡在等待输入上。跑完之后sqlmap会直接告诉你哪几个参数是注入点、用的什么注入类型、后端数据库是什么还会顺手把当前库名dump出来。这里要提醒一个新手常犯的错误URL里的参数值到底要不要写有一种说法是sqlmap会自动替换参数值所以写不写无所谓。实际操作中我建议保留一个合法的参数值比如id1因为有些后端会先校验参数格式你传一个空值过去直接触发参数校验逻辑导致请求提前返回sqlmap就会误判为不可注入。2.3 请求级参数是调通的起点很多注入点不只存在于URL参数里POST请求体、JSON格式的接口、XML格式的接口都可能存在注入。sqlmap通过--data参数来指定请求体python sqlmap.py -u http://target.com/api/login --datausernameadminpassword123456 --batch如果是JSON接口加一个请求头指定Content-Type结合--data传JSON字符串python sqlmap.py -u http://target.com/api/user --data{id: 1, name: test} --headersContent-Type: application/json --batch遇到需要特定请求头的接口使用--headers统一指定多个头部多个值之间用换行分隔python sqlmap.py -u http://target.com/search?id1 --headersUser-Agent: Mozilla/5.0\nX-Forwarded-For: 127.0.0.1 --batch还会遇到需要指定HTTP方法的情况比如DELETE或PUT直接加--method参数python sqlmap.py -u http://target.com/api/item/delete --methodPOST --dataid1 --batch在我处理过的案例里调通请求这一步往往比后续利用花的时间还多。原因很简单真实业务系统的请求上下文比较复杂包括Cookie、Token、时间戳签名、业务幂等参数等一个请求头缺失就可能导致整个测试流程白费。我通常会先用Burp Suite完整抓一个能正常返回的请求包看清楚所有请求头和请求体再按需用--cookie、--headers、--data把这些信息喂给sqlmap。3. sqlmap核心参数体系详解按场景选参数而不是背参数很多人学sqlmap喜欢死记参数结果真到用的时候不知道该组合哪几个。我换个思路按测试阶段和场景来拆参数这样你拿到任何目标都能快速判断该用什么组合。3.1 探测配置参数--level与--risk--level和--risk是sqlmap里最容易让人懵的两个参数它们直接决定测试深度和payload范围。--level控制的是测试的粒度和覆盖范围取值范围1到5默认1。level 1只测GET和POST参数level 2会加测Cookie参数level 3会加测User-Agent和Referer请求头level 4和5会进一步扩大payload范围包括更复杂的边界条件和特殊的注入位置。--risk控制的是payload的风险等级取值范围1到3默认1。risk 1是最常规的测试risk 2会增加一些heavy query类型的测试这类payload会对数据库造成明显负载risk 3会增加基于OR的注入测试这种payload容易影响原查询逻辑对数据有一定风险比如把一个WHERE条件变成恒真可能把整表数据查出来。我日常测试默认用--level3 --risk2这两个参数组合能在覆盖率和误报率之间取得平衡。只有在时间非常充裕或者目标数据不太重要的情况下才会考虑把level调到5。不过这里有一个明显的坑level和risk调高后测试时间会指数级增长一个原本10分钟跑完的注入点可能变成2小时而且还会产生大量垃圾请求容易被WAF盯上。实际项目中更稳妥的做法是先用默认level跑一遍快速判断是否存在注入确认存在后再针对这个注入点单独拉高level做深度探测。盲目一上来就拉满level既不高效也容易被封。3.2 注入技术类别参数--techniquesqlmap支持的注入技术类别用一个字符串标注包含BBoolean-based blind、EError-based、UUnion query-based、SStacked queries、TTime-based blind、QInline queries默认全部开启优先级排序是U E B S T Q。这个优先级设计是有道理的因为不同的注入方式效率天差地别。联合查询注入能把结果直接回显到页面里报错注入能通过数据库报错内容把信息带出来它们都算高效类型。布尔盲注需要根据页面内容变化逐字符猜解时间盲注更慢每次都要等固定的延迟时间所以sqlmap默认不优先用它们。拿真实的DVWA low等级注入来举个例子这个靶场环境里最常见的注入类型就是联合查询但是很多人在跑sqlmap时发现它一直卡在布尔盲注上速度很慢。原因是这类注入点在页面回显上有个特点sqlmap启发式判断优先选择了最保守的方式。这时候你完全可以手动指定python sqlmap.py -u http://127.0.0.1/dvwa/vulnerabilities/sqli/?id1SubmitSubmit --cookiePHPSESSIDxxx; securitylow --techniqueU --batch只允许联合查询注入速度会快很多。反过来如果目标应用把所有数据库报错信息都屏蔽了页面也没有任何联合查询的回显位置那你只能靠时间盲注指定--techniqueT来跑虽然慢但是有效。3.3 数据获取参数从库名到数据一条龙这里的内容大多数人比较熟悉但我想重点强调参数组合的完整链路因为很多人只会单独执行某一步。--dbs列出目标所能访问的所有数据库。测试第一步往往就是它用于快速判断目标数据库资产的规模。--current-db查看当前应用连接的数据库。后续操作里你应用最多的也是这个库。-D 库名 --tables指定库名后列出该库的所有表。-D 库名 -T 表名 --columns列出指定表的全部字段名及其数据类型。-D 库名 -T 表名 -C id,username,password --dump把指定字段的数据全部导出来。实际测试时我不会一上来就--dump因为很多生产环境的表有几十万行数据dump过程耗时很久还容易把数据库压力打高触发告警。正确的做法是先查询行数确定数据规模再决定要不要全量dumppython sqlmap.py -u http://target.com/item.php?id1 -D appdb -T users --count如果返回只有几百行直接dump如果发现是几百万行可以加条件限定python sqlmap.py -u http://target.com/item.php?id1 -D appdb -T users -C id,username,md5_password --dump --start1 --stop100--start和--stop控制从第几行开始到第几行结束适合大表抽样测试。还有个实用参数是--dump-all测试目标数据量不大时可以直接把所有库表结构一次性拉出来但这个操作动静极大务必在授权明确的情况下使用因为dump过程会发送大量高负载SQL语句很容易被安全设备发现。3.4 绕过与免杀参数--tamper的实战运用--tamper是sqlmap里最有深度的参数之一它的作用是对payload做前置变形处理从而规避输入过滤和WAF检测。sqlmap内置了上百个tamper脚本每个脚本的变形逻辑不同。这里列几个我用得最多的脚本名作用适用场景space2comment空格替换成/**/绕过过滤空格的逻辑between比较符号换成BETWEEN ... AND ...绕过对的过滤equaltolike替换成LIKE绕过对等号赋值类语句的过滤base64encodepayload做Base64编码后端存在Base64解码逻辑时ucommend关键字中间插入混淆注释绕过关键字过滤例如SEL/**/ECTcharencode普通字符URL编码绕过简单关键字匹配型WAFapostrophemask单引号替换成UTF-8编码绕过单引号过滤用法很简单python sqlmap.py -u http://target.com/search.php?kwtest --tamperspace2comment,equaltolike --batch多个tamper脚本用逗号连接按顺序执行。这里强烈建议你动手之前先自己打开这些脚本的源码看一眼搞清楚每个脚本到底对payload做了什么变形。因为Web应用过滤规则五花八门靠记忆匹配这个WAF用哪个tamper是不现实的你得能根据实际报错判断是哪里被拦截了再选择对应的脚本。还有一个重要事实tamper并非万能的。现在很多WAF是语义分析引擎你单纯把空格换成注释、把关键字拆开它一样能识别出恶意意图。遇到这类强WAF更可靠的方法是人工分析它的规则设计针对性绕过比如利用数据库特性、利用协议解析差异等这些就不是单纯套tamper能解决的。3.5 执行控制参数跑得快和跑得稳如何兼顾--threads控制并发线程数默认1。注意sqlmap官方文档明确说不建议把threads设太高因为很多注入场景下并发会引入条件竞争导致检测结果不确定。实际使用中我最多开到3只有确认是绝对稳定的联合查询注入时才敢开到5以上。--delay每两个HTTP请求之间人为睡眠的秒数。测试目标有频率限制时必须开这个参数建议1到3秒避免被目标风控设备识别为扫描行为。--timeout单个HTTP请求的超时时间默认30秒。如果目标网络质量较差建议调低一些比如10秒避免每个请求都卡满超时。--retries连接失败后的重试次数默认3。在目标服务器性能不好时这个参数要适当调低因为重试会叠加请求量。--batch所有交互询问都自动选默认值。批量扫描时的必备参数不加它脚本会在第一次交互时永久卡住。--smart只对启发式判断存在潜在注入可能的参数做深度测试。这个参数在批量扫描时非常有用能过滤掉明显没有注入的参数减少大量无用请求。-o开启所有优化开关等价于--predict-output --keep-alive --null-connection的组合。开启后能显著提升测试速度代价是部分时间盲注场景下输出结果可能不够精确。--check-waf检测目标是否存在WAF。跑批量任务前先跑一下这个能帮你提前判断当前目标环境下payload会不会被拦。在不清楚目标防御措施的前提下我建议你宁可慢一点也别一上来就开--threads和-o。踩过的坑已经不少了有一次我对某个目标站点直接默认参数跑结果对方WAF在20秒内就把我的测试IP封了后来加上了--delay2和随机User-Agent才恢复正常测试。4. 批量扫描方案让sqlmap从工具变成测试流程单点测试是基础真实项目里你面对的是几十个URL、上百个参数这时候手工一条条敲命令效率太低必须设计一套批量扫描方案。4.1 多URL批扫--url-file的正确用法sqlmap原生支持批量扫描URL文件先用文本文件把所有目标URL按行整理好http://target.com/page1.php?id1 http://target.com/page2.php?cid2uid3 http://target.com/search.php?kwtest然后执行python sqlmap.py --url-file urls.txt --batch --smart --level3 --risk2这个组合的作用是逐个URL做测试smart参数会先做轻量级启发式判断有潜力的参数才进入深度测试避免在明显没注入的点上浪费时间。这个方案有个缺陷不支持并发。URL多且每个URL响应速度较快时串行执行的耗时依然很大。我的做法是手动写一个小循环脚本按CPU核心数做简单并发控制。下面的shell脚本把URL列表切成多个分片后台并行跑sqlmap同时限制后台任务数量#!/bin/bash URL_FILEurls.txt SPLIT_COUNT4 OUTPUT_DIRsqlmap_results mkdir -p $OUTPUT_DIR split -n l/$SPLIT_COUNT $URL_FILE $OUTPUT_DIR/url_part_ for part_file in $OUTPUT_DIR/url_part_*; do python sqlmap.py --url-file $part_file \ --batch --smart --level3 --risk2 \ --output-dir$OUTPUT_DIR \ --random-agent --delay1 done wait echo 批量扫描完成拆成4个分片后台最多同时跑4个sqlmap进程互相不干扰。每个进程独立输出结果到自己的目录全部跑完后统一收拢结果。这个方案跑几百个URL也只是时间问题不会把你手绑在终端前。4.2 从Burp Suite导请求包直接喂给sqlmap工作中最省事的方式其实是复用Burp Suite抓到的请求包。你在浏览器或App里操作业务功能时把所有相关的HTTP请求历史保存下来然后用sqlmap的--log-file参数直接读取python sqlmap.py -l burp_log.txt --batch --smartsqlmap会从日志文件里自动解析出每个请求的URL、参数、Cookie、请求头等信息逐个做注入测试。这个方案的好处很明显请求上下文完整不需要你手工维护URL列表和Cookie而且覆盖了POST、JSON、XML等所有请求类型。实际操作中还有一个进阶玩法先把Burp的历史请求集中发到目标接口通过业务路由把所有可能带参数的接口都触发一遍再把这些请求导给sqlmap这样就能覆盖整个应用的所有注入点。不过从log文件跑批量时一定要加--batch否则sqlmap在检测出同一个注入点时停下来问你这个点已经测试过了要不要继续没人在旁边确认时任务就卡死在半路。4.3 单URL多参数点的自动化扩展还有一种常见情况输入点在同一个接口里但有多个参数比如搜索接口有kw、category、page等参数它们可能都存在注入。sqlmap默认只会测你URL里带的值对应的参数如果想让sqlmap自动把所有参数都测一遍可以在URL里把所有参数值留空或用合法值python sqlmap.py -u http://target.com/search?kwtestcategory1page2 --batch --smartsqlmap会自己遍历整个参数集合逐个做注入判断。这里需要说明的是参数值最好填合法值避免触发参数校验逻辑。还有一个参数--skip可以用来排除特定参数比如page这个参数是纯数字分页你分析过不可能存在注入那就加--skippage减少无用功。4.4 批量任务的结果整理与召回率校验批量方案跑完结果不是终点。sqlmap默认把结果存在~/.local/share/sqlmap/或--output-dir指定的目录里面的日志文件命名方式是时间戳加目标URL的哈希值。直接在原始目录里翻结果会非常痛苦我通常会写一个简单的find命令把记录每个目标结果的target.txt内容列出来find sqlmap_results -name target.txt -exec sh -c echo {} ; head -c 1000 {} \;更实用的做法是在批量扫描前就规划好输出目录结构按目标应用名建子文件夹扫完直接去对应目录查结果。这里还需要注意一个关键点批量扫描之后sqlmap给出的Injectable结论不一定全对有些高并发下容易产生误报尤其是时间盲注类。所以我有一个固定习惯所有sqlmap批量报出来的注入点都会用单个URL单独重跑一遍确认稳定复现后再判断是否真的存在漏洞。尤其是高危的OS-Shell类结果必须人工二次确认。4.5 并发与速度的平衡策略批量扫描最忌讳的是贪快。你开了10个并发每个进程又设置--threads4那目标站点瞬间收到几十个请求每秒很容易触发熔断、限流、封IP最终所有的测试中止。我在实际项目中总结出一个相对稳妥的节奏目标类型并发进程数--threads--delay本地靶场或测试环境430内部系统低防护221对外业务系统可能有WAF112耐心把参数调好批量任务跑得久一点没关系比起被封IP导致全部重来慢一点反而更高效。5. 实测中的意外情况与完整排查链路5.1 场景一sqlmap报no parameter(s) found for testing这个提示出现时很多人第一反应是目标不存在注入但实际上有一半情况是请求上下文没调通。我第一次遇到时排查了很久后来发现是目标站点把未带指定Cookie的请求全部重定向到了首页sqlmap拿到的响应全是首页内容自然无从判断注入。解决办法是在请求参数中补全Cookie信息python sqlmap.py -u http://target.com/sqli.php?id1 --cookiesessionidabc123; user_level2 --batch另一种情况是URL里的参数值格式不对。有的业务系统要求参数必须是数字或UUID格式你传一个1进去它能正常返回但sqlmap替换成payload后后端直接返回参数格式错误sqlmap会认为这个参数没有可测试性。解决办法是用一个足够长的、满足格式要求的合法值比如一个真实的UUID再加上--level5强制测试。还有一种隐蔽情况目标接口存在HSTS或强制HTTPS跳转你给的URL是http开头的所有请求都被302到了https地址sqlmap默认不会跟随重定向去测试新地址。加--force-ssl或者在URL里直接写https协议就能绕过这个问题。排查建议按顺序检查请求是否带齐了鉴权信息、参数值是否合法、协议是否被强制跳转、是否有反爬校验。90%以上的找不到注入点问题出在这四类原因。5.2 场景二注入点确认存在但--dbs迟迟不出数据这种情况大部分是时间盲注。shell界面显示it is recommended to perform only on the affected parameter然后卡在一个字符一个字符地猜解一个库名要跑好几个小时。我的处理思路先按CtrlC中断当前任务然后用--techniqueU或E重新指定高效注入技术再试一次。如果目标确实只存在时间盲注那就要想办法确认所有请求是否真的落在同一个注入点上以及目标数据库返回包是否存在网络抖动。此时可以缩小范围到单库python sqlmap.py -u http://target.com/item.php?id1 --dbmsmysql --techniqueT --current-db --batch --time-sec3--time-sec控制时间盲注时每个payload的延迟秒数如果网络延迟不稳定建议调高到5秒避免误判。另一个常见问题是当前数据库账号权限太低只能看到information_schema里的一部分内容。此时换成更高权限账号的连接串或确认下当前注入点是否有文件读写权限再决定下一步往哪个方向利用。5.3 场景三WAF拦截表现不是封IP而是假性无注入很多WAF的配置是不封IP但会把包含明显SQL注入特征的请求直接拦截并返回一个200配合一段安全提示。sqlmap一看返回码是200且页面内容相似就会认为payload没有生效继而判断为非注入点。怎么识别这类情况在测试前先用--check-waf看一眼目标有没有WAF再通过--safe-url给sqlmap提供一个正常的页面地址让它定时访问这个普通页面来确认当前请求路径本身是可访问的。更直接的办法是加上详细日志观察python sqlmap.py -u http://target.com/item.php?id1 --check-waf --batch -v 6-v 6会打印每个测试payload的请求与响应摘要肉眼一看就知道是不是所有请求都被同一个特征内容拦截了。确认被WAF拦截后就该上tamper了。先用最温和的变形脚本逐个试注意这里不要一上来就是一堆tamper叠在一起因为tamper叠加变形可能把payload改得面目全非数据库反而解析不了导致误判。5.4 场景四大表dump半路中断实际测试中经常会遇到dump大表时任务中断可能是网络断了、目标主动断开连接、或者WAF开始限流。中断之后重新跑sqlmap会从上次的记录断点续传。之前测试一张60万行的用户表时跑了4个小时才到40%因为中间有段时间delay设置过小导致请求超时频发。后来我调整了参数--timeout10--retries1--delay1从断点继续跑稳定了很多。这里给一个通用的稳健参数组合python sqlmap.py -u http://target.com/item.php?id1 -D appdb -T users --dump --timeout10 --retries1 --delay1 --batch5.5 所有批量结果都要做的终验动作批量扫描不管怎么优化机器判断永远有概率出错。我的习惯是批量扫完后对每个疑似注入的URL单独再跑一次最小的确认命令python sqlmap.py -u http://target.com/xxx.php?id1 --batch --smart --stringFound echo [] Confirmed如果目标应用存在报错回显可以用--identify-waf和--current-user快速验证此注入点是否真实可利用。这一步能过滤掉大部分因并发导致的时间盲注误报是测试报告中数据可靠性的最后一道保险。6. 绕不过的技术与绕得过的立场sqlmap确实很强大但它本质上只是一个辅助工具真正决定测试质量的是你怎么理解目标、怎么组合参数、怎么排查异常。这篇文章里提到的参数组合和批量扫描方案都是我在授权测试项目里一点一点捋出来的你可以直接拿去参考但不要盲目照搬因为每个目标的网络环境、应用特征、防御手段都不一样。学习SQL注入的初期强烈建议先在本地搭好靶场DVWA、SQLi-Labs、pikachu都是很好的练手环境。在这些环境里把手工注入、sqlmap自动化注入、基于WAF的绕过变形都过一遍再考虑去实际的授权测试中应用。没有授权就去测试别人的系统那不是技术问题是立场问题。我个人的体会是sqlmap用久了容易产生依赖但它再快也不会替代你分析注入原理的能力。真正遇到奇奇怪怪的过滤规则、遇到sqlmap测不出来的场景时能救你的还是你对SQL语法、数据库特性、HTTP协议的理解。工具给效率思考给深度两者缺一不可。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进