← 返回文章列表

SQL 注入依然排名第一:参数化查询才是根因解

安全数据库避坑

问题根源

把用户输入直接拼进 SQL 字符串,攻击者在输入里写 ' OR '1'='1 就能改变语义,读出、篡改甚至删掉数据。

根因解:参数化查询

把「代码」和「数据」分开传:占位符 ? 或命名参数让数据库始终把它当数据,永远拼不进命令。

// 错误:字符串拼接
db.query("SELECT * FROM u WHERE name = '" + name + "'")

// 正确:参数化
db.query("SELECT * FROM u WHERE name = ?", [name])

两个常见错觉

  • ORM 一定安全:用原生 SQL 拼接的 ORM 同样危险,raw query 也要参数化;
  • 转义就够:手动转义容易漏、易错,参数化更稳。

实战案例:三个依然常见的注入面

  1. ORM 的 raw 查询:ORM 本身不是护身符,只要写了 raw("... WHERE id=" + id) 就照样能被注入。凡是拼接字符串的地方都要改成绑定参数。
  2. LIKE 与排序字段:LIKE '%"+kw+"%' 会注入;ORDER BY "+col 的参数化在很多驱动里不被支持,必须用白名单映射字段名,绝不能直接拼。
  3. 二次注入:用户输入先转义后入库,读取时又被拼进新 SQL——此时数据里的引号已“洗白”。根因仍是拼接,参数化才是解。

常见问题(FAQ)

参数化能防住所有注入吗?能防住“数据被当成命令”,但如果表名、列名本身来自用户输入,仍需白名单校验。转义函数为什么不够?它与字符集、驱动实现强相关,稍有不一致就会漏,而参数化把边界交给驱动处理。只读账号就不用防了吗?读也能造成数据泄露,且一旦被写成写操作就晚了;应遵循最小权限并始终参数化。日志里能打 SQL 吗?可以,但要打参数化模板与参数值,别拼接后再打,避免日志二次流入其它系统。

纵深防御:把注入的影响面压到最小

  1. 最小权限:应用账号只授予必要的库表与 DML 权限,禁止 DROP、FILE 与跨库查询,即使被注入也难以造成毁灭性后果;
  2. 统一数据访问层:把数据库访问收敛到一层封装,禁止业务代码直接拼 SQL,从流程上消灭拼接入口;
  3. 代码评审与静态检查:把“字符串拼接 SQL”列为必查项,用静态规则扫描 raw(、字符串加法与模板插值;
  4. 输入白名单而非黑名单:对枚举类参数(排序字段、状态值)直接用白名单映射,不接受自由文本;
  5. 监控与告警:对异常查询模式(大量 UNION、SLEEP、报错激增)设告警,注入尝试通常在成功前会先留下痕迹。

边界场景的取舍

  • 动态表名 / 列名:参数化无法覆盖,必须用白名单映射;不要试图用转义解决;
  • 批量插入:使用驱动提供的批量接口(如 VALUES (?,?),(?,?) 的参数数组),而不是循环拼字符串;
  • 模糊搜索:把通配符加在参数值里("%" + kw + "%" 作为参数),而不是拼进 SQL 文本;
  • 存储过程:内部若仍用字符串拼接动态 SQL,同样存在注入,需一并审查。

事后处置

一旦怀疑被注入:先保全日志与流量证据,再立即轮换数据库凭据并缩小权限,随后审计近期数据变更与出网连接,最后才是修代码与补测试。顺序颠倒会破坏证据链。

测试与回归方法

  • 用例库:建立包含引号、注释符、联合查询片段与编码变体的固定测试集,随接口一起回归;
  • 边界字段优先:排序字段、表名映射、模糊搜索参数是最容易漏检的位置,应单独写测试;
  • 自动化扫描:把 SQL 注入扫描接入 CI,能在合并前发现新增的拼接写法;
  • 线上验证:上线后用只读查询确认应用账号无法执行 DDL,验证最小权限确实生效。

修复优先级

发现注入点后,先堵住对外可触达的入口(尤其是无需登录即可调用的接口),再排查内部接口;同时轮换可能已泄露的凭据、审计近期异常查询。修复顺序应是“止血 → 取证 → 根因修复 → 补测试”,避免先改代码导致现场被破坏。