4. 注入攻击与防御
注入类漏洞的本质,是应用把“用户可控的数据”错误地当成了“可执行的代码或结构”。SQL注入让数据进入SQL语法树,NoSQL注入让数据进入查询文档或操作符,命令注入让数据进入Shell命令行,ORM注入则让数据绕过ORM封装、进入底层查询片段。四者载体不同,但根因一致:数据与代码之间缺少不可跨越的边界。本节围绕这四类注入,讲清原理差异,并落到参数化查询、输入验证与最小权限三项可执行实践。
一、四类注入的边界在哪里被打破
SQL注入的典型触发点是字符串拼接。例如登录查询写成 "SELECT * FROM users WHERE name='" + name + "'",当输入为 ' OR '1'='1 时,引号闭合了原字符串,OR 条件恒真,查询语义被改写。防御的核心不是过滤引号,而是让数据库驱动把参数当作纯数据绑定,即参数化查询(预编译语句)。
NoSQL注入常见于MongoDB等文档数据库。若后端直接接收JSON对象并传入查询,攻击者可用操作符改写条件:
// 危险:直接使用请求体对象
db.users.find({ username: req.body.username, password: req.body.password })
// 攻击载荷
{ "username": "admin", "password": { "$ne": null } }
此时 $ne 使密码条件变为“不等于null”,从而绕过校验。防御要点是类型与结构校验:明确要求 username、password 为字符串,拒绝对象、数组和以 $ 开头的键。
命令注入发生在应用调用系统命令时。例如 exec("ping -c 1 " + host),输入 127.0.0.1; cat /etc/passwd 会追加执行第二条命令。防御首选避免拼接Shell,改用参数数组形式调用,如 execFile("ping", ["-c","1",host]),并对 host 做严格白名单校验。
ORM注入容易被忽视:ORM本身通常安全,但一旦使用原生SQL拼接、whereRaw、orderByRaw 或把用户输入拼进排序字段,就会退化为SQL注入。例如 orderByRaw("id " + sort),输入 asc; DROP TABLE users-- 即可造成破坏。ORM注入的防御原则是:能用查询构造器就不用原生片段;必须用原生片段时,绑定参数而非拼接。
二、参数化查询:把数据钉死在参数位
参数化查询的关键在于SQL语句结构先被数据库编译,参数值随后绑定,值永远不会改变语法结构。以Python为例:
# 危险
cur.execute("SELECT * FROM users WHERE name='%s'" % name)
# 安全:占位符绑定
cur.execute("SELECT * FROM users WHERE name=%s", (name,))
Java JDBC 使用 PreparedStatement 与 ? 占位符;Node.js 使用 ? 或命名占位符;Go 使用 $1。注意:表名、列名、排序方向不能作为普通参数绑定,这类动态结构必须走白名单映射,例如把前端传入的 sort=name 映射到固定列名集合,而不是直接拼接。
三、输入验证:在进入查询前收紧形状
输入验证不是替代参数化,而是补充。有效做法包括:
- 类型校验:id 必须是整数,username 必须是长度1–32的字符串,拒绝对象与数组,直接阻断 NoSQL 操作符注入。
- 白名单枚举:排序字段、状态值、角色名只允许预定义集合,如
["id","name","created_at"]。 - 格式约束:IP、邮箱、UUID 用正则或专用库校验,命令注入场景下 host 只允许合法IP或域名。
- 拒绝以
$开头的键:在解析JSON时递归检查,防止$where、$ne等操作符进入查询。
四、最小权限:让注入即使发生也炸不大
最小权限是纵深防御的最后一道闸。具体落地:
- Web应用连接数据库的账号只授予业务必需的
SELECT/INSERT/UPDATE/DELETE,禁止DROP、FILE、GRANT。 - 不同业务使用不同数据库账号,读写分离,报表账号只读。
- 应用进程以低权限系统用户运行,禁止以 root 执行;容器内使用非root用户。
- 命令执行场景限制可执行程序白名单,禁用
sh -c拼接。
- 所有SQL是否使用占位符绑定?是否存在字符串拼接查询?
- 动态表名/列名/排序是否走白名单映射?
- 请求体中的对象、数组、
$开头键是否被拒绝? - 系统命令是否使用参数数组调用而非Shell拼接?
- ORM中是否出现
whereRaw、orderByRaw等原生片段?参数是否绑定? - 数据库账号权限是否最小化?是否禁止DDL与文件读写?
把四类注入放在一起看,防御路径是统一的:先让数据无法变成代码(参数化、安全API),再让数据形状可控(类型与白名单验证),最后让越权后果有限(最小权限)。三者叠加,才能覆盖从语法改写、操作符注入到命令追加的完整攻击面。