SQL 注入系统学习(六):特殊注入点与组合场景
本文仅用于 CTF、靶场、本机实验和明确授权环境。
SQL 注入不只出现在 ?id=1。排序字段、搜索通配符、JSON 列清单、Cookie、请求头,以及从数据库再次取出的旧数据,都可能进入动态 SQL。
1. ORDER BY 注入
危险代码:
1 | $sql = "SELECT id,name,price FROM products |
正常输入:
1 | price |
这里的输入是列名或表达式,不是普通字符串值。
观察方法:
1 | 切换列名后结果顺序是否变化 |
排序注入通常不改变响应长度,只改变顺序。
防御:
1 | $allowed = [ |
排序方向 ASC/DESC 也要白名单。
2. LIKE 搜索
危险代码:
1 | $q = $_GET['q']; |
需要区分:
1 | SQL 注入 输入改变 SQL 结构 |
参数化查询可以阻止结构注入,却不会自动把 % 和 _ 当普通字符。如果业务要求字面匹配,还需要单独处理通配符转义。
3. 登录框
1 | $sql = "SELECT id,role FROM users |
检查:
1 | 用户名和密码哪个字段进入查询 |
不要把弱口令、默认账户、认证逻辑错误、NoSQL 注入和越权都叫作 SQL 注入。
4. LIMIT 与 OFFSET
1 | $sql = "SELECT * FROM articles |
分页参数容易被误认为“天然只能是数字”。安全做法:
1 | 转换并验证为非负整数 |
5. 动态表名与分表
1 | $table = 'logs_' . $_GET['month']; |
表名属于 SQL 结构,普通参数占位符通常不能代替它。应将业务值映射到固定表名,并限制数据库账户只访问必要对象。
6. JSON API
1 | { |
分析:
1 | id、filter 常作为数据值,可以绑定 |
使用 JSON 只改变输入格式,不会自动消除 SQL 注入。
7. IN 数组
危险写法:
1 | $ids = implode(',', $_GET['ids']); |
安全思路:
1 | 验证每个元素类型 |
把整个逗号字符串绑定到一个占位符,通常也不能自动形成多个 IN 元素。
8. Cookie
1 | $token = $_COOKIE['token']; |
Cookie 由客户端携带,不能因为它不在 URL 中就默认可信。
9. 请求头
1 | $ua = $_SERVER['HTTP_USER_AGENT']; |
1 | $ip = $_SERVER['HTTP_X_FORWARDED_FOR']; |
User-Agent、Referer、X-Forwarded-For 都可能由客户端控制。X-Forwarded-For 只有在可信反向代理正确覆盖时才具有可信意义。
10. ORM
ORM 通常能安全处理普通值,但危险点包括:
1 | raw query |
“使用 ORM”不是“没有 SQL 注入”的充分证据。
11. 存储过程
调用存储过程时使用参数,并不代表过程内部安全:
1 | SET @sql = CONCAT('SELECT * FROM ', input_table); |
如果过程内部继续拼动态 SQL,注入仍然存在。
12. INSERT 注入
1 | INSERT INTO messages(username,content) |
页面可能只返回“提交成功”。观察点可能在:
1 | 数据库错误 |
13. UPDATE 注入
1 | UPDATE users |
输入可能影响赋值表达式、其他字段或 WHERE 条件。必须使用可恢复测试数据。
14. 二次注入
第一阶段安全保存:
1 | INSERT INTO users(username) VALUES (?); |
第二阶段错误拼接:
1 | $oldName = load_name_from_database(); |
即使第一步参数化,数据在第二步被重新当成 SQL 片段,仍会触发漏洞。
排查过程:
1 | 找到可写字段 |
修复原则:每个 SQL 执行点都必须安全构造,数据库中的旧数据并不天然可信。
15. 堆叠查询
1 | SELECT 1; SELECT 2; |
是否能够一次执行多条语句,取决于:
1 | 数据库驱动是否允许 multi statements |
常见误区:
1 | 分号没有报错 ≠ 第二条语句已执行 |
涉及修改、删除数据或改变数据库对象时,应使用可重置的专门靶场。
16. 审计清单
1 | [ ] URL、POST、JSON、Cookie 和请求头是否全部追踪 |
下一篇收束 MySQL 文件能力、SQL mode、版本与驱动差异,并给出整套做题和修复决策树。