很多人第一次接触 SQL 注入,都是从一个登录框开始的。在靶场里敲下那行流传已久的万能密码,页面直接跳进后台,那一刻确实挺震撼。但真到了实际项目里,光会手抄 payload 是撑不住的——你面对的是一个带着各种参数的接口、可能被 WAF 挡住的请求、以及几十个字段的数据库。Sqlmap 就是处理这些脏活累活绕不开的工具。
这篇文章我会按“原理 → 环境 → 上手 → 进阶 → 防御 → 排错”的顺序,手把手带你把 Sqlmap 用明白。无论你是刚入门的萌新,还是已经会跑--dbs但遇到偏门注入点就卡壳的老手,都适合照着文章完整过一遍。我后面会持续把这个指南补全,你在使用中有搞不定的地方,也可以在评论区把现场情况丢出来,我看到了会继续更新。
1. 注入点是怎么出现的:从万能密码讲到拼接 SQL 的本质
1.1 一个登录框为什么会沦陷
先看一段最典型的错误代码。假设后端用 Python 写了一个登录接口,接收前端传来的用户名和密码:
username = request.form.get('username') password = request.form.get('password') sql = "SELECT * FROM users WHERE username = '%s' AND password = '%s'" % (username, password) cursor.execute(sql)如果用户老老实实输入admin和123456,这段 SQL 最终会长这样:
SELECT * FROM users WHERE username = 'admin' AND password = '123456';问题在于:username和password的内容被直接拼进 SQL 字符串,没有做任何过滤或转义。攻击者输入的不是一个“值”,而是一段“SQL 代码片段”。
比如在用户名字段输入:
admin' OR '1'='1' --拼接到最终语句里就是:
SELECT * FROM users WHERE username = 'admin' OR '1'='1' -- ' AND password = '123456';--在 MySQL 里表示注释,注释符后面的password条件直接被忽略。而前面的OR '1'='1'恒为真,整个 WHERE 条件就成了永远成立。查询结果返回第一行用户,程序往往只校验“有没有查到记录”,于是直接放行。这就是万用密码最原始的形态。
这个例子的核心逻辑一句话就能说清:你原本想让用户提供数据,结果用户连数据库查询逻辑也一起提供了。
1.2 注入的分类决定了你用 Sqlmap 的姿势
光知道“注入能绕过登录”远远不够,因为注入点返回给攻击者的信息量是不同的。按照利用方式,大致可以分成几类:
- 联合查询注入(UNION-based):页面会把 SQL 查询结果直接显示出来,你可以用
UNION SELECT拼出额外数据。Sqlmap 会优先尝试这种,因为它效率最高、一次能带出很多数据。 - 报错注入(Error-based):数据库报错信息直接回显到页面,通过让报错内容携带查询结果来取数据。
- 布尔盲注(Boolean-based):页面只返回“真/假”两种状态,比如正常显示和空白页。Sqlmap 会通过不断调整条件,逐字符猜出数据。
- 时间盲注(Time-based):页面不返回任何区别,Sqlmap 通过判断每次请求的响应时间变化(比如是否延时了 5 秒)来确认结果真假。
- 堆叠注入(Stacked queries):在原有 SQL 后追加额外语句,能用但限制很多,不同数据库对它的支持差异很大。
了解这个分类很关键。因为你在跑 Sqlmap 的时候,它可以同时检测全部技术,也可以指定只用某一种;当默认组合检测不出来、或者被 WAF 拦到怀疑人生时,手动指定技术类型往往能峰回路转。后半篇我会专门讲这个操作。
1.3 “会手写 payload”和“会用工具”是两件事
我见过不少新人学注入,第一步就急着装 Sqlmap,结果跑完--dbs也不知道自己在干什么。我个人建议的顺序是:先能手画明白一个最简单的手工注入流程,再让工具替你加速。因为 Sqlmap 的本质是自动化采集和判断,它替你做了“检测是否存在注入 → 确认注入类型 → 爆库 → 爆表 → 爆字段 → 拖数据”这一整套流程。如果你连每一步对应什么数据都不知道,碰到工具跑不动的情况就完全没抓手。
反过来,如果你已经能手工确认注入点存在,再用 Sqlmap 就轻松多了——你可以在命令里明确告诉它“走布尔盲注”“数据库是 MySQL”,它就不用浪费大量请求去猜。工具不是用来代替你思考的,是用来把你想清楚的事情快速执行出来的。
2. 花 20 分钟搭一个练手靶场:本地环境准备
2.1 Sqlmap 本身怎么装
Sqlmap 是纯 Python 写的开源工具,所以它对操作系统非常友好。部署方式分两种情况:
情况一:你用的是 Kali Linux。系统自带 Sqlmap,打开终端直接敲:
sqlmap --version能输出版本就说明可以直接用了。这也是为什么很多安全教程都喜欢用 Kali 演示,省掉了安装环节。
情况二:你在 Windows 或普通 Linux 上。推荐直接把源码拉下来用。Windows 下先装好 Python 3.8 以上版本,然后到目标目录执行:
git clone https://github.com/sqlmapproject/sqlmap.git cd sqlmap python sqlmap.py --version没有 Git 的话,也可以去 GitHub 页面的 Releases 或 Code 页面下载 ZIP 包解压,本质上是一样的。
2.2 靶场选什么:我为什么推荐 sqli-labs
练习注入最重要的不是“跑通命令”,而是“有地方可以反复试验不担责”。我强烈建议用sqli-labs作为第一个靶场。原因有三:
- 它是专门为 SQL 注入设计的,几十个关卡从入门到进阶排列得清清楚楚,每一关对应不同的注入类型和参数位置;
- 它是 PHP 写的,对运行环境要求低,配一个 PHP 集成环境就能跑;
- 它是很多教材、课程默认的示范靶场,遇到问题能搜到的资料最多。
另一种常见选择是 DVWA(Damn Vulnerable Web Application),它也包含 SQL 注入模块,而且环境更完整,还能练文件上传、XSS 等其他漏洞。但对纯新手来说,DVWA 的页面功能太杂,sqli-labs 更“纯粹”一些——每一个关卡就是一道注入题。
2.3 sqli-labs 部署的完整步骤
我以 Windows 上最常见的集成环境为例。先安装 phpStudy 或 XAMPP,这类工具会把 Apache、MySQL、PHP 一次性配好,省掉逐个安装的麻烦。然后:
- 把 sqli-labs 源码下载后解压到网站根目录,phpStudy 默认是
C:\phpstudy_pro\WWW,XAMPP 默认是C:\xampp\htdocs,总之保证浏览器能通过http://127.0.0.1/sqli-labs/访问。 - 打开源码根目录下的
sql-connections/db-creds.inc,这是数据库连接配置文件。检查 $dbuser、$dbpass、$dbname 是否和你的 MySQL 一致。 - 浏览器访问
http://127.0.0.1/sqli-labs/,页面会出现一个 Setup/reset Database for labs 的按钮,点一下,它会自动创建靶场需要的数据库和表。 - 然后进入第一关,地址类似
http://127.0.0.1/sqli-labs/Less-1/?id=1。
如果你 Linux 环境已经装好了 Docker,也可以用容器拉起整个靶场,整体更干净。搭建完以后,要记住你的靶场 URL 是什么样的格式,因为后面每一条 Sqlmap 命令都要基于对应的 URL 来写。
2.4 环境里最容易忽略的两个细节
第一,PHP 版本不要太新。部分老版本的 sqli-labs 对 PHP 8 的兼容性有问题,如果安装后页面出现大量报错,建议把环境切到 PHP 7.x 再试。第二,确认防火墙没有挡住本地回环地址。有次我在一台装了杀毒软件的 Windows 上跑题,Sqlmap 访问127.0.0.1正常,但访问靶场绑定的内网 IP 时连接超时,折腾了半天才发现是防火墙拦截了入站规则。
还有一点:练手时请务必用本地靶场,不要拿线上真实站点去试。道理很简单——你没有授权的情况下扫描和注入是明确越界的行为。靶场存在的意义就是让你放心大胆地跑,跑挂了随时重置,这比任何“求稳”的侥幸都可靠。
3. 第一波实战:从检测注入点到列出全部数据库
3.1 最基础的一条命令长什么样
假设靶场第一关的 URL 是:
http://127.0.0.1/sqli-labs/Less-1/?id=1那么 Sqlmap 的第一条命令可以写成:
sqlmap -u "http://127.0.0.1/sqli-labs/Less-1/?id=1" --batch参数解释一下:
-u指定要测试的 URL,这是 Sqlmap 最核心的基础参数;--batch表示全程默认选择,遇到询问直接回车同意的等价效果,不需要人工干预。
跑起来之后,Sqlmap 大概会经历这么几个阶段:
- 先检查目标 URL 是否可访问;
- 对
?id=1这个参数做各种注入条件的试探,包括单引号、逻辑表达式等; - 输出检测到的注入类型;
- 询问是否要进一步获取数据库信息。
如果一切顺利,你会在结果里看到类似Parameter: id (GET)、Type: UNION query或Type: boolean-based blind这样的输出。看到这些,说明注入点已经被确认了。
--batch的实际意义是让整条命令可以不中断地跑完。但注意:它会把“是否测试其他参数”“是否尝试进一步利用”这类提问全部默认掉,所以你可能漏掉一些延伸选项。刚开始学的时候,我建议不要加--batch,让工具每个问题都停下来,你亲眼看一遍它问了什么、选了什么,这对建立直觉非常有帮助。等你熟练了再回归批处理模式。
3.2 从注入点到数据库名的完整链路
确认注入点存在后,下一步就是“拖库”。Sqlmap 提供了一层一层的命令,典型链路是这样的:
# 获取所有数据库 sqlmap -u "http://127.0.0.1/sqli-labs/Less-1/?id=1" --dbs # 获取当前数据库 sqlmap -u "http://127.0.0.1/sqli-labs/Less-1/?id=1" --current-db # 指定数据库,列出所有表 sqlmap -u "http://127.0.0.1/sqli-labs/Less-1/?id=1" -D security --tables # 指定表,列出所有字段 sqlmap -u "http://127.0.0.1/sqli-labs/Less-1/?id=1" -D security -T users --columns # 指定字段,导出数据 sqlmap -u "http://127.0.0.1/sqli-labs/Less-1/?id=1" -D security -T users -C username,password --dump这串命令的规律很简单:定位的单位从大到小,一层比一层具体。数据库名用-D,表名用-T,字段名用-C,导出用--dump。如果你不指定-C,Sqlmap 会把整张表的所有字段数据都导出来。
我在新手期踩过的一个坑是:拿到--dbs结果后,不知道选哪个库。靶场环境可能列出一堆系统库加业务库,比如information_schema、mysql、performance_schema,以及真正的目标库security。不要一头扎进系统库里,先看名字——业务相关的库往往就是你的目标。
3.3 传递参数也能这样用
很多人以为 Sqlmap 每次都要重新检测注入点,这其实很浪费。Sqlmap 支持在命令行里直接追加已有的测试信息,最典型的是:
sqlmap -u "http://127.0.0.1/sqli-labs/Less-1/?id=1" -D security -T users --dump --batch它会自动读取本地上一次的会话记录,如果参数没变、URL 没变,就直接复用之前确认的注入类型,省去重新探测的等待时间。
另外,你还可以用--flush-session清除某 URL 的缓存会话。在靶场环境里不改动的情况下一般用不上,但在调试脚本时,如果改动了后端代码或过滤逻辑,不清会话可能会导致 Sqlmap 沿用旧的判断结果,误以为注入点还是老样子。
4. 场景决定参数:GET、POST、登录表单与请求头的不同打法
4.1 GET 注入:-u一个参数解决
上一节用的?id=1就是最典型的 GET 注入。GET 参数直接体现在 URL 里,Sqlmap 自动就能扫到。但如果 URL 里有多个参数,比如?id=1&cat=2&sort=desc,你觉得只有id是注入点,可以指定参数:
sqlmap -u "http://target/page.php?id=1&cat=2" -p id --batch-p的作用是精确告诉 Sqlmap:只测我指定的参数,别浪费时间去试其他字段。这在实际项目里很有用,因为有些参数明明是业务字段(比如分页大小、排序方式),你扫它没意义,还可能触发异常行为。
4.2 POST 登录表单:从手写数据到-r读取请求包
大部分登录框是 POST 提交的,参数不在 URL 里。这时有两种写法。
第一种是直接用--data:
sqlmap -u "http://target/login.php" \ --data="username=admin&password=123456&submit=Login" \ --batch这里容易犯的错是:登录成功页和登录失败页展示的内容不一样,Sqlmap 布尔盲注依赖页面差异来判断真假,所以它可能搞不清楚到底哪些变化是注入导致的。我常用的做法是把登录请求的目标定位到“登录接口本身”,并观察是否返回“用户不存在”或“密码错误”之类的自定义反馈——这种反馈本身就是判断依据。
第二种更贴近实战的写法是抓请求包保存成文件,然后用-r读取:
POST /sqli-labs/Less-11/login.php HTTP/1.1 Host: 127.0.0.1 User-Agent: Mozilla/5.0 Content-Type: application/x-www-form-urlencoded username=admin&password=123456&submit=Login把上面内容保存为req.txt,然后:
sqlmap -r req.txt --batch为什么推荐这种方式?因为真实项目里请求往往带着大量请求头、Cookie、Token,用手写--data很容易漏掉鉴权信息,导致 Sqlmap 一跑就 401 或跳验证码。用-r可以把整个请求原封不动喂给工具,最重要的一点是,它连请求头的顺序和细节都保持了原样。
登录框场景里还有个常见需求是专门测密码字段。如果前端对用户名和密码做了不同处理(比如密码字段被强制转义过),你可以指定只测密码:
sqlmap -r req.txt -p password --batch4.3 Cookie 注入与请求头注入:默认扫不到,要加 level
Sqlmap 默认只检测 GET/POST 参数,Cookie 和 Header 里的注入点需要调高测试等级才会被扫到。测试等级由--level控制,范围是 1~5,默认是 1,只测 GET 参数;到 2 会加入 Cookie;到 3 会加入 User-Agent 和 Referer 等请求头字段。
因此如果你怀疑某个 Cookie 字段(比如uid、session_id)存在注入,命令可以这么写:
sqlmap -u "http://target/page.php" --cookie="PHPSESSID=abcd1234; uid=1" --level=3 --batch我遇到过很多次这种情况:GET 参数扫完一遍干干净净,结果把--level调到 3 之后,发现 Cookie 里的uid直接被注入了。很多站点对 URL 输入做了防护,却忘了 Cookie 同样会被拼进 SQL。这也是 Sqlmap 这类工具存在的意义之一——它按“参数位置”穷举测试,而不是只测人一眼能想到的入口。
4.4 场景总结:一张表看清每个参数干什么
| 场景 | 核心参数 | 典型命令 |
|---|---|---|
| GET 参数注入 | -u | sqlmap -u "http://t/?id=1" |
| POST 表单注入 | --data或-r | sqlmap -u "http://t/login" --data="user=a&pass=b" |
| 指定注入字段 | -p | sqlmap -r req.txt -p password |
| Cookie 注入 | --cookie+--level>=2 | sqlmap -u "http://t/" --cookie="uid=1" --level=3 |
| Header 注入 | --level>=3 | sqlmap -u "http://t/" --level=3 |
| 请求文件复用 | -r | sqlmap -r req.txt |
5. 进阶玩法:绕过 WAF、盲注加速与 Tamper 脚本
5.1 为什么有时候 Sqlmap 明明跑完却报“没注入点”
新手最容易崩溃的时刻就是:手工加单引号页面报错,Sqlmap 跑完却告诉你 target is not injectable。原因基本有两类。
第一类是检测的信号被干扰。比如页面本身就存在变化(动态时间组件、随机的 token、广告位刷新),导致布尔盲注的“真”和“假”区分不出来。第二类是请求被 WAF 或防护规则拦截。Sqlmap 的默认 payload 特征非常明显,像UNION SELECT、SLEEP()这类字眼在 WAF 规则里基本都是重点关照对象,拦截后工具看到的只是“正常页面”,自然判断不出注入。
遇到这种情况,第一件事不是换工具,而是分辨到底是被拦还是真没洞。你可以用浏览器插件或抓包工具对比手工 payload 和正常请求的响应状态码、响应长度,如果手工数据包和正常包的状态码有明显差异(403、406 这类),基本就是拦截了。
5.2 指定注入技术类型:别再让它盲目试探
当默认的“全类型检测”跑不出来时,你可以根据自己对目标代码的直觉,强制 Sqlmap 只用某一种技术:
# 只用布尔盲注 sqlmap -u "http://t/?id=1" --technique=B --batch # 只用时间盲注 sqlmap -u "http://t/?id=1" --technique=T --time-sec=10 --batch--technique后面的字母分别对应:B(布尔盲注)、E(报错注入)、U(联合注入)、S(堆叠注入)、T(时间盲注)。组合写法比如--technique=BEUST代表全部尝试。强制指定某一种技术主要是为了减少请求量,比如一个页面输出非常规律,布尔盲注的判断会很快,没必要让工具再去试联合查询。
时间盲注的--time-sec要特别注意:默认的等待时间在 5 秒左右。如果目标网络延迟高,你设置为 3 秒可能会出现大量误判;反过来,如果目标响应本身很快,5 秒又会拖慢整个测试,具体值要看现场调整。
5.3 Tamper 脚本:Sqlmap 的“变脸”机制
Tamper 脚本的作用是对 payload 做变换,让它绕过 WAF 的规则库。与其说它是高科技,不如说它是一套“改装车间”。Sqlmap 在tamper/目录下提供了几十个现成脚本,我常用的有这几类:
- 空格替换类:比如
space2comment.py把空格变成/**/,针对很多 WAF 表达式里“不允许出现非预期符号”的规则; - 关键字等价类:比如
equaltolike.py把=变成LIKE,绕过对=的敏感检测; - 大小写混排类:比如
randomcase.py随机改变关键字大小写,绕过简单的大小写匹配规则。
组合使用示例:
sqlmap -u "http://t/?id=1" --tamper="space2comment,between,randomcase" --batch用法上有个实际教训:不要一次挂太多脚本。Tamper 脚本转换是有层数的,脚本之间可能产生冲突,也可能因为变换后的 payload 过长导致长度超限。我的习惯是先挂一个最对症的(比如目标 WAF 拦截空格就挂 space2comment),不管用了再加。
5.4 压速度和降风险:level、risk 和 delay
--level和--risk是很容易搞混的一组参数。--level控制的是测试覆盖面,比如要不要测 Cookie、Header、各种边界情况,数值越大会尝试越多的注入位置和 payload 变体;--risk控制的是测试激进程度,比如OR 1=1这类有破坏性风险的 payload。官方默认是level=1 risk=1,你可以在必要时升到level=5 risk=3,但要明白升级意味着请求数量成倍增加,跑一个 URL 可能需要很长很长的时间。
控制测试节奏用--delay:
sqlmap -u "http://t/?id=1" --level=3 --risk=2 --delay=1 --batch--delay=1表示每个请求之间间隔 1 秒,主要是为了降低对目标的访问频率,避免触发防扫描机制。很多防护系统会根据短时间内的请求频率封 IP,限速是最简单的保命手段。
6. 一个容易被忽略的能力:看懂 Sqlmap 输出并转成漏洞报告
6.1 输出的每一行都是有含义的证据
很多人拿到 Sqlmap 输出只看最后有没有数据,中途的日志直接跳过。这其实很亏,尤其是做渗透测试交付的时候,Sqlmap 的原始输出就是你的测试证据,没有它,你的报告就没有说服力。
以一条典型输出为例:
Parameter: id (GET) Type: boolean-based blind Title: AND boolean-based blind - WHERE or HAVING clause Payload: id=1' AND 7490=7490-- nBve这段告诉你的信息是:
- 注入参数是
id,位置是 GET; - 注入方式是布尔盲注;
- 工具实际发送的测试载荷长这样。
这些信息就是你写报告中“漏洞详情”那一栏的原料。你需要把它们转化成开发能理解的语言:“/page.php?id=参数未经过滤拼接进 SQL,布尔盲注可验证,可读取数据库内容。”
6.2 报告模板可以很简洁但必须完整
我通常会在交付中给一个表格,列五样东西就够了:
| 项目 | 内容 |
|---|---|
| 漏洞 URL | http://target/page.php?id=1 |
| 注入参数 | id(GET) |
| 注入技术 | boolean-based blind |
| 影响范围 | 可枚举当前库所有表结构与数据 |
| 修复建议 | 使用预编译语句 / 参数化查询替代拼接,强制整型校验 |
这五列信息一填,开发转给后端基本不用再来回问。如果发现的是登录框类型的注入,我还会额外提醒一句“不仅是密码可绕过,还可能通过联合查询直接取用户表数据,建议同步检查认证逻辑”。
6.3 Sqlmap 的“另一半”:它测出来的数据如何交给后续流程
很多实战流程里,Sqlmap 不是终点。比如你--dump出一堆密码 Hash,Sqlmap 本身不会帮你解密,这很正常。我的做法是把 Hash 保存成文件,丢到本地跑字典或暴力破解,而不是在目标上反复尝试登录。在目标机器上做密码爆破是风险极高的行为,转为本地离线处理既安全又高效。
另外,Sqlmap 的--dump结果默认会保存到本地会话目录,格式是 CSV 或文本。如果你需要做进一步数据处理,比如批量对比多个库的数据,可以在命令里追加--dump-format=csv指定输出格式,方便导入表格工具。
7. 工具能帮你拖库,你还得会堵库:从攻击视角切到防御
7.1 参数化查询:把数据当数据,别当代码
Sqlmap 能把库拖出来的前提,是目标代码把用户输入直接拼进了 SQL。反过来讲,只要数据不再以“代码片段”的身份进入 SQL,注入就自然不存在了。最有效的修复手段是参数化查询——先定义 SQL 骨架,再把变量当作参数传入,数据库会严格区分“结构”和“数据”。
用 PHP 的 PDO 改造最前面那个登录例子:
$stmt = $pdo->prepare('SELECT * FROM users WHERE username = :username AND password = :password'); $stmt->execute(['username' => $username, 'password' => $password]);用 Java 的 JDBC 也一样:
PreparedStatement ps = conn.prepareStatement( "SELECT * FROM users WHERE username = ? AND password = ?"); ps.setString(1, username); ps.setString(2, password);这样做之后,用户哪怕输入admin' OR '1'='1' --也不再是“SQL 代码”,而是被当作一个普通字符串值去比较,什么都匹配不上。很多现代框架如 Laravel、Spring Boot、Django 如果规范使用 ORM,默认也是参数化的。真正出问题的,往往是那些历史悠久、经历过几手维护的老代码,以及被开发顺手写出来的原始字符串拼接。
7.2 输入校验:别把鸡蛋全放在一个篮子里
参数化是主力防御,但你不应该只靠它。第二层保险是输入校验。比如对id这种业务上本身就应该是整数的参数,直接强制转换:
id = int(request.args.get('id', 0))对邮箱、手机号这类字段用正则校验。一把思路是:能枚举的不要放开,能白名单的不要用黑名单。允许合法字符集之外的东西直接被拒绝掉,比费力维护一堆恶意关键字黑名单要可靠得多。
需要特别提醒的是“黑名单过滤”这个思路。很多开发者喜欢把'、--、OR、SELECT这些关键字过滤掉就算防注入,但事实上攻击者可以大小写混写、注释隔断、编码绕过,黑名单迟早要被绕穿。只依赖黑名单等于把安全建立在漏洞库的完善程度上,这不是防御,是赌博。
7.3 数据库权限:用最小权限治理
即使代码有漏洞,如果数据库连接用的是最小权限账号,攻击者能看的范围也会被压到很小。比如一个展示文章列表的页面,理论上只需要SELECT权限,为什么要给它一个能DROP TABLE的账号?
实操建议分两步:
- 每个应用单独建一个数据库账号,只授权必要的库表权限;
- 明确拒绝不需要的权限,比如
DROP、DELETE、FILE。
这能极大降低注入被进一步利用的风险。Sqlmap 的--is-dba参数就是用来探测当前数据库用户是否为超级管理员,如果连的是 root 权限,等于直接把整个数据库的控制权交给了攻击者。
7.4 报错信息:离攻击者再远一点
数据库报错信息本身就是注入判断的“信号灯”。手工测试时,You have an error in your SQL syntax这类明文报错几乎等于在告诉攻击者“我这里有 SQL 拼接”。防御上的做法是:关闭生产环境的错误回显,统一返回统一友好的错误页面。同时,把详细错误写进服务端日志而不是响应体。
要注意这里有个矛盾点:关闭报错会把“报错注入(Error-based)”的路子堵住,但布尔盲注、时间盲注依然存在,所以报错处理只是辅助,不是防火墙。核心还是参数化。
8. 我在实际项目中踩过的 Sqlmap 坑:排错对照表
8.1 请求半天返回 connection refused
最常见的不是注入没检测到,而是目标根本没连通。先分几步排查:
- 浏览器直接访问同一个 URL,确认目标服务在线;
- 确认网络路径可行——如果目标在内网,你的机器是否在同一内网、是否被防火墙拦截;
- 确认 URL 是否有鉴权,Sqlmap 带着空 Cookie 和浏览器带 Cookie 完全是两种结果。
如果是登录后才有数据的页面,先用-r带上浏览器的完整请求,或者明确加上--cookie、--header,把会话信息原样传过去。
8.2 检测超时或特别慢
注入检测慢一般有几种原因:目标本身慢、网络延迟高、触发的时间盲注等待时间长、或者检测等级太高导致请求量爆炸。优先检查网络,其次按需限技术类型。比如你已经确认是布尔盲注,就指定:
sqlmap -u "http://t/?id=1" --technique=B --threads=5 --batch--threads表示并发请求线程数,默认是 1,适当调高能极大提速,但注意不要太夸张——并发过高会把目标打挂,也可能触发防护系统封 IP。
8.3 Sqlmap 判定 not injectable,但手工能注
这种情况我见过很多次,99% 都和“页面输出不稳定”或“请求被拦截”有关。处理思路:
- 先确认请求有没有到后端,用抓包工具对比正常请求和注入请求的响应差异;
- 如果被 WAF 拦截,升级 Tamper 方案或降低请求频率;
- 如果是因为页面内容一直在变化(比如动态 TP 组件),可以试试
--string参数,指定一个稳定出现的字符串作为“真”的基准。
--string的例子是这样:假设正常页面里始终有 “Welcome” 这个词,而注入判断的页面不存在时才没有,你可以:
sqlmap -u "http://t/?id=1" --string="Welcome" --batch这告诉 Sqlmap:只要响应里有 Welcome,就认为条件是“真”。这招在页面动态化严重的场景里非常管用。
8.4 输出乱码或中文丢失
有些老站点用的字符集不是 UTF-8,--dump出来的中文会变成乱码。遇到这种情况先检查目标数据库的字符集,然后指定输出编码。Sqlmap 对这类问题的处理能力有限,我的建议是拿到原始数据后如果还是乱码,就换到数据库客户端里用正确编码查询对比,别在工具输出层面较劲。
8.5 会话缓存导致的误判
如果你在前面跑过一次注入测试,后续改了后端代码或换了参数结构,没有清理会话缓存就再次运行,Sqlmap 很可能直接复用旧会话里的判断结果,显示“injectable”或“not injectable”,但其实已经失效了。这时加一句:
sqlmap -u "http://t/?id=1" --flush-session强制重建会话即可。
说实话,我用 Sqlmap 这些年,真正帮上忙的反而往往不是那句最复杂的命令,而是“知道哪条参数在回应什么现场信号”。工具永远在更新,参数也会变多,但判断思路是不会过时的——先确认注入点存在,再明确注入类型,再决定技术参数怎么选,最后把结果翻译成别人能听懂的漏洞描述。
这篇文章我会保持更新,后续打算把二次注入、堆叠注入的 Sqlmap 用法,以及更多 WAF 绕过实例补进来。你们在实际使用中卡在哪个环节,评论区直接描述一下场景,我会陆续补充上去。