现代orm框架无法杜绝sql注入,因raw()、text()等原生接口绕过参数化,且字段名、表名、order by等结构化内容无法参数化,须白名单校验。

ORM 本身不自动防注入,它只在你「用对方式」时才起作用。一旦配置或写法偏离安全路径,比如启用原生 SQL、关闭参数化、或动态拼接结构部分,防护就形同虚设。
SQLAlchemy 中 text() 不带命名参数直接执行原生 SQL
很多人以为用了 text() 就算“走 ORM 安全通道”,其实不然。如果只是把字符串塞进去,没用 {"param": value} 方式传参,数据库收到的就是完整拼接后的语句。
- 危险写法:
session.execute("SELECT * FROM users WHERE name = '" + name + "'")—— f-string 或+拼接都等价于裸 SQL 注入入口 - 正确写法:
session.execute(text("SELECT * FROM users WHERE name = :name"), {"name": name}) - 注意:
:name必须和字典键名严格一致;?占位符在 SQLAlchemy 的text()中不被识别,会报错
Django raw() 和 extra() 跳过 ORM 编译器
这两个方法本质是绕过 Django 的参数化机制,直接生成 SQL 字符串。哪怕只拼一个字段名或条件值,就等于把解析权交还给数据库引擎。
- 错误示例:
MyModel.objects.raw(f"SELECT * FROM myapp_mymodel WHERE status = '{status}'") - 正确写法:
MyModel.objects.raw("SELECT * FROM myapp_mymodel WHERE status = %s", params=[status]) -
extra()的where参数同样危险:.extra(where=["name = '" + user_input + "'"])→ 必须改用params传参,且仅支持位置参数
MyBatis 使用 ${} 替换而非 #{}
${} 是字符串替换,不是参数化;#{} 才触发 PreparedStatement 绑定。这个区别在 XML 映射文件里极易忽略,尤其当开发者想“动态表名”或“动态排序字段”时手抖选错。
- 高危写法:
SELECT * FROM ${tableName} WHERE id = ${id}—— 表名和值全被拼接 - 安全写法:
SELECT * FROM user WHERE id = #{id}(值安全)+ 表名必须硬编码或白名单校验 - 排序字段如
ORDER BY ${sortField}无法参数化,必须限制为if sortField not in ["created_at", "name"]这类白名单判断
Hibernate HQL / Criteria 中混入用户控制的属性名
HQL 本身支持参数化,但属性路径(如 user.name)若来自用户输入,就会绕过所有防护。Hibernate 不会对属性名做转义,它直接编译成 SQL 字段引用。
- 危险操作:
String hql = "FROM User u WHERE u." + fieldName + " = :value"→ 攻击者传fieldName=class.classloader.resources.context可能触发反射链 - 安全做法:字段名必须来自预定义映射表,例如
Map.of("username", "u.username", "email", "u.email"),再查表取值 - Criteria API 更安全,但它也依赖你不用
Root.get(requestedField)这种动态获取方式
最常被忽略的是:ORM 防护只覆盖「值」,不覆盖「结构」。表名、列名、函数名、ORDER BY、GROUP BY、甚至 HQL 中的类名——只要由用户输入决定,又没走白名单或静态映射,参数化就完全失效。











