← 中文资源

简体中文版本

用攻击测试证明 SQL 注入防护有效

盘点所有查询拼接点,逐个用经典载荷攻击,并加回归测试让参数化保持诚实。

阅读英文原文

目标

注入之所以存活,是因为它长得像功能:字符串拼对所有合法输入都能工作。找到它的审计必须是进攻式的:枚举所有 SQL 文本组装点、对每个点发射经典载荷、观察返回了什么。参数化让载荷失效;任何返回多余行或执行尾随语句的地方都是发现,而每个发现都变成永久的回归测试。

步骤要点

  1. 用 rg 扫描 query/execute 调用里的模板插值与字符串拼接,过滤掉已是参数化的调用,得到候选清单。
  2. 对候选点逐一投喂经典载荷:OR 恒真、分号 DROP、UNION 脱库、注释截断。
  3. 发现的漏洞一律改为占位符绑定修复,绝不靠转义修补。
  4. 把载荷语料固化成回归套件:断言结果集只含与载荷字面量匹配的行。
  5. CI 里重跑拼接扫描,新的查询构造站点在评审之前就构建失败。

排错提示

动态 ORDER BY 无法绑定值,就把可排序列做成白名单,校验通过后才插值标识符;ORM 的 raw 片段仍是拼接,按所用 ORM 的逃逸口专用名去搜并同样攻击;载荷对不同方言失效就补方言专用样本(PG 美元引号、MySQL 注释技巧)。

适用边界

通过语料只证明对这些载荷免疫,扫描加评审依然必要;二阶注入(存储值再读取)要求每个读取点都参数化,不只是写入点。

代码示例

const ATTACKS = ["' OR '1'='1", "1; DROP TABLE users;--", "' UNION SELECT password FROM users--"];

// The fix: values are bound, never concatenated
const SAFE = "SELECT id, name FROM users WHERE name = $1";
const rows = await pool.query(SAFE, [userInput]);