当页面什么都不显示时,数据还能被读走吗:盲注的 1 bit 通道
网络安全实战 90 天 · Week 3 注入风险 | 难度 ★★★ 进阶 | 关键词:盲注 二分法 响应时间 信息泄露 请求频率告警
前面讲的招式都建立在「页面会把结果打出来」这个前提上。把前提拿掉——页面只说「存在」或「不存在」——数据还能不能泄露?能,但速率会被压到一个非常低的水平。理解这个速率上限,比记住 payload 更有价值:它同时是操作的代价,也是防护的抓手。
一、前提被拿掉之后剩下的东西
把响应里所有内容剥掉,能观察的只剩三种:状态码、响应体有没有某段固定文字、响应耗时。这三样都是一比特的信息量。盲注的全部技巧,就是围绕「怎么把这 1 bit 用满」展开的。
| 可观察量 | 取值范围 | 单次请求承载的信息 |
|---|---|---|
| 状态码 | 2xx / 4xx / 5xx | 约 1 bit |
| 固定文字出现与否 | 有 / 无 | 1 bit |
| 响应耗时 | 正常 / 明显延迟 | 1 bit(噪声较大) |
说明本文全部操作在自建靶场(本机 Docker 中的 DVWA)完成,仅用于理解信息泄露的最小条件。真实环境中的同类验证必须在授权范围内,并严格控制请求频率——盲注本身的性质就决定了它会产生大量请求,这在任何生产系统上都是可被监测的行为。
二、第一步:确认到底有几种状态
先别急着构造条件。拿两个明显不同的输入去撞一下,看页面会不会出现可区分的变化。这一步不成立,后面全做不了。
靶场的盲注页面里,输入 1 会显示一句「用户存在」,输入 999 什么都不显示。这就是仅有的两个状态。
三、第二步:把判断条件接进去
确认存在两个状态之后,把一个恒真条件和一个恒假条件接上去。能区分开,布尔型通道就建立了。
bash
1' AND 1=1 -- 1' AND 1=2 --第一条显示「存在」,第二条没有任何输出。两个状态稳定可区分。
四、第三步:先猜长度,再猜字符
直接逐字符猜成本极高。先用长度把范围压到一个有限枚举,再逐位确定字符,总请求数能少一个数量级。
bash
1' AND LENGTH(database())=3 -- 1' AND LENGTH(database())=4 -- 1' AND LENGTH(database())=5 --只有等于 4 时页面显示「存在」,长度确定为 4。
五、第四步:用二分法把每个字符压到 7 次以内
逐字符试是线性成本,二分是 log 成本。把字符转成 ASCII 数值,用大于 / 小于把区间砍半——可打印字符一共 95 个,7 次足够定位一个。
bash
1' AND SUBSTRING(database(),1,1)='d' -- 1' AND ASCII(SUBSTRING(database(),1,1))>100 -- 1' AND ASCII(SUBSTRING(database(),1,1))=100 --首字符 ASCII 为 100,对应小写 d;同样方法依次得到后续三位,拼起来就是一个 4 字符的库名。
5.1 这一步真正的收获
同样的信息,换个问法,成本掉一个数量级。这个观察不只属于注入——任何「只能得到是 / 否」的信息通道,二分都是标准优化。把这句话反过来读,就是防护思路:只要单次请求能拿到的信息量足够小,读取就必然慢,慢就必然留下日志。
六、第五步:当连两个状态都没有时,用时间
更严苛的情况是页面在条件成立与不成立时返回完全一致。这时唯一还能观察的就是耗时:让数据库在条件成立时停顿几秒。
bash
1' AND SLEEP(5) -- 1' AND IF(LENGTH(database())=4,SLEEP(5),0) -- 1' AND IF(SUBSTRING(database(),1,1)='d',SLEEP(5),0) --第一条确认延时函数可用;后两条把条件包进分支里,条件成立才停顿。信息通道从「页面文字」换到了「响应时间」。
6.1 为什么必须先测基线
网络本身有抖动,2 秒和 5 秒在链路上不好分。做法是先发一个条件恒假的请求,测出正常响应耗时作为基线,再和延时请求对比;阈值拉大到 10 秒以上,误判率会明显下降。这一步在真实环境里尤其重要——跨地域访问的抖动可能比延时函数本身还大。
七、交给工具,但要知道它在干什么
手工走这一遍是为了理解原理,真实场景不可能一位一位敲。工具可以按技术类型限定检测方式,布尔型和时间型分别对应不同的参数。
bash
sqlmap -u "http://127.0.0.1/vulnerabilities/sqli_blind/?id=1&Submit=Submit" \ --cookie="PHPSESSID=你的值; security=low" --technique=B --dbs工具用布尔盲注跑出的库名,与手工结果一致。同一件事,手工一个多小时,工具几分钟——差别只在于它把循环写好了。
防护侧记盲注的请求量是它的致命弱点。几十到几百次同源、同参数、几乎等间隔的请求,在访问日志里形态非常突出。所以对这类行为,基于频率与相似度的告警比基于内容的规则更有效——请求方可以变换 payload,但变不了「必须问很多次」这件事。
八、踩坑记录
| 坑点 | 症状 | 解决办法 |
|---|---|---|
| 两个状态一模一样 | 页面完全无差异 | 改用时间通道,或检查是否选错了参数 |
| 延时函数不生效 | 响应一直几百毫秒 | 被禁用,换等价的耗时函数 |
| 延迟判断不准 | 2 秒和 5 秒分不清 | 阈值加到 10 秒,先测一次基线 |
| 跑在 POST 接口上扫不到 | 工具报无注入 | 改传表单数据,参数位置要对 |
| 手工太慢放弃 | 猜一个字符几十秒 | 改用二分法,或限定检测技术交给工具 |
盲注慢是物理限制,不是工具写得差。每次请求只带 1 bit,这条下限改不了。
基线比 payload 重要。不测基线的延时判断,全是猜的。
手工做一遍的价值在于知道工具报「慢」的时候它在等什么。
九、小结
盲注的本质是把一次查询拆成很多次是非题,速率上限由信息论决定。
二分法用同样的信息换来了数量级的效率提升——这是全文最值得带走的一个技巧。
时间通道依赖基线测量,跳过基线等于放弃可信度。
从防护角度看,盲注的「必须高频请求」是可检测特征,频率与相似度告警比内容规则更稳。
下一篇看过滤与拦截:当输入里的特殊字符被处理掉之后,哪些防线会先失效。