ORM框架不能完全免疫SQL注入,因其仅在安全使用时有效;调用raw()、execute()等原生SQL接口会绕过参数化机制,若拼接用户输入(如f-string、${}、未校验的标识符)即触发风险。

为什么 ORM 框架不能完全免疫 SQL 注入
ORM 框架本身不“自动防注入”,它只在**你按安全方式使用时才起作用**。一旦调用框架提供的原生 SQL 接口(如 raw()、execute()、text()、query_raw()),就等于绕过 ORM 的参数化机制,把拼接权交还给开发者。
哪些 ORM 原生接口容易出问题
不同框架暴露的危险接口命名不同,但本质一致:接受字符串 SQL + 可选参数,且**不强制参数化**。常见高危调用包括:
- Django:
Model.objects.raw('SELECT * FROM user WHERE name = "' + name + '"')或cursor.execute('UPDATE user SET age = ' + age_str) - SQLAlchemy:
session.execute(text("SELECT * FROM users WHERE id = " + user_id))(未用:param占位符) - Laravel Eloquent:
DB::select(DB::raw("SELECT * FROM users WHERE email = '$email'")) - MyBatis:
<script>SELECT * FROM user WHERE name = '${name}'</script>(注意是${}而非#{})
参数化 ≠ 写了参数就安全
即使用了原生接口的“参数”功能,也常因写法错误失效:
- 把变量直接拼进 SQL 字符串里,再传参 —— 参数根本没机会介入解析,例如:
session.execute(text(f"SELECT * FROM logs WHERE level = '{level}'")) - 用 Python 的
f-string或.format()拼接表名/列名 —— 这些无法参数化,数据库驱动不支持运行时替换标识符 - 在 MyBatis 中误用
${}(字符串替换)代替#{}(预编译占位)——${table_name}会直插进 SQL,等同于拼接 - 把用户输入传给
ORDER BY或GROUP BY子句 —— 大多数 ORM 不允许参数化这些位置,需白名单校验
真正安全的替代方案
遇到必须动态控制结构的场景(如动态表名、排序字段),不要硬塞进原生 SQL,改用框架内置的安全构造方式:
- Django:用
Q()对象组合查询,或order_by()传字段名字符串(前提是已校验是否在白名单中) - SQLAlchemy:用
getattr(Model, field_name)获取列对象,再传给order_by(),避免字符串拼接 - Laravel:用
when()链式条件,或对$request->input('sort')做严格白名单判断:in_array($sort, ['name', 'created_at'], true) - 通用原则:标识符(表名、列名、关键字)永远不来自用户输入;若必须动态,只允许从预设数组中选取
最易被忽略的一点:ORM 安全性完全取决于你是否让 SQL 结构和数据彻底分离。只要出现一次 "' + user_input + '" 或 ${user_input},前面所有参数化努力就归零。

















