SQL 注入的演变与参数化查询
SQL 注入有二十年历史,防御方案早已成熟,但新代码仍在不断引入老漏洞——因为拼接的诱惑始终存在。
漏洞的最小形态
// 危险:用户输入直接进入 SQL 文本
$sql = "SELECT * FROM users WHERE name = '" . $_GET['name'] . "'";
输入 ' OR '1'='1 改变了语句结构,'; DROP TABLE users; -- 更是能执行任意语句。本质是数据与代码的边界消失:数据库无法知道哪部分是程序员的意图、哪部分是用户数据。
演变出的对抗
防御方引入过滤(关键字黑名单)→ 攻击方用注释绕过(SEL/**/ECT)、编码变换、大小写。黑名单注定失败。参数化查询(预处理)从协议层建立边界,才是终结方案。
具体原理见上一篇《PDO 预处理到底防住了什么》:参数走独立通道,永远不会被解析为 SQL 语法。
现代代码里它还长什么样
ORM 让拼接变得隐蔽:
# 危险:f-string 进 ORM 的 raw 查询
User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'")
# 安全:参数化
User.objects.raw("SELECT * FROM users WHERE name = %s", [name])
动态排序、动态表名、报表系统的动态条件拼接,是注入在新代码里的主要藏身处。评审规则:值用参数化,标识符用白名单,没有例外。
已有系统怎么鉴定
- 全局搜索字符串拼接 SQL 的模式(
+ $.f"紧邻 SELECT/INSERT); - 用 sqlmap 对外部接口做一次只读探测(注意授权与影响,只在测试环境);
- 开启数据库审计,观察异常语句特征(恒真条件、注释符、UNION)。
纵深防御
参数化是第一道。之后还应该:
- 数据库账号最小权限:业务账号不给 DROP/FILE 权限;
- 不同应用独立账号,出事可定位可止损;
- 错误信息不回显 SQL——报错页泄露表结构是注入的加速器;
- WAF 作为外层补充,但不能依赖:编码绕过与 WAF 的攻防永无止境,参数化才是釜底抽薪。
一句话总结:注入防御没有新故事,故事只有一句——不要把不可信数据拼进 SQL 文本。