SQL 注入实战:时间盲注原理与 Python 脚本详解

前言

前两篇文章介绍了布尔盲注------它依赖页面在"正确"和"错误"时呈现不同内容。但如果目标页面对任何输入都返回完全相同的响应,布尔盲注就彻底失效了。

这时候就轮到时间盲注(Time-based Blind SQLi) 登场:我们不再观察页面内容的变化,而是让数据库"睡一觉",通过测量响应时间来判断条件真假。只要服务器响应慢了,我们就知道答对了。

文章目录

靶场题目: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

靶场搭建请参考:

至此,布尔盲注与时间盲注的完整系列告一段落,届见。

免责声明:本文所有内容仅供授权环境下的安全学习与研究,切勿将相关技术用于任何未经授权的系统。

相关推荐
2603_969694855 天前
JDBC 基础学习笔记:API 核心原理解析与 CRUD 代码实操
数据库·笔记·mysql·学习方法·database·sql注入
隔窗听雨眠5 天前
数据库访问权限治理的分层设计与落地实践
数据治理·权限管理·数据库安全
FungLeo7 天前
成为全栈·基础补充·Web 安全基础:XSS、CSRF 与 SQL 注入
web安全·网络安全·xss·csrf·sql注入·成为全栈
kingx311 天前
BugKu PRA 渗透测试 2(Harry‘s Blog):9 Flag 全链实战复盘
中间件·渗透测试·log4j2·内网渗透·反序列化
kingx311 天前
BugKu 渗透测试 4 实战:文件上传打点,5 Flag 全通
数据库·渗透测试·web·内网渗透
文章永久免费只为良心14 天前
Web应用安全与防护_第三章_日志文件包含_从一道 CTFshow 题目讲透「日志投毒型 LFI → RCE」:原理、时序陷阱与全变种攻防手册
android·网络安全·渗透测试·漏洞危害·漏洞描述·日志投毒·红队指南
IT大白鼠15 天前
渗透测试实战从靶场搭建到渗透流程——第 8 篇 · 服务型靶标(上):MySQL 双版本写文件链
数据库·mysql·渗透测试
sbjdhjd15 天前
从污点追踪断点到 PHP-FPM 请求队列:HIDS PHP WebShell 样本复盘
开发语言·网络安全·数据挖掘·渗透测试·开源·php·shell
能年玲奈喝榴莲牛奶16 天前
4zreark:只用 PostgreSQL 的资产侦察与漏洞管理平台,Go + React 全栈开源
网络安全·渗透测试·资产测绘·企业管理·资产收集
天府云创17 天前
Rustscan 端口扫描工具入门到实战
渗透测试·网络扫描·nmap·网络嗅探·rustscan·安全测速·企业攻防