orm不能百分百防sql注入,因其仅在标准方法(如filter、where)中启用参数化;raw()、text()、db::raw()、${}、extra()、annotate()、cursor.execute()等接口绕过参数化,且表名、字段名、order by等结构化部分无法参数化,须白名单校验。

ORM 框架本身不防注入,它只在你「没乱来」时才帮你挡下最基础的 WHERE 值注入;一旦碰上原生 SQL、动态结构或私有 API,防护就直接失效。
哪些 ORM 接口会跳过参数化机制
这些调用等于主动退出安全区,数据库收到的就是拼好的完整 SQL 字符串:
-
raw()(Django)、text()(SQLAlchemy)、DB::raw()(Laravel)——只要字符串里含+ name或f"WHERE id = {id}",就已失守 -
extra()和annotate()配合Func()或字典传参(如extra={'decimals': request.GET.get('precision')}),参数直接插进模板生成 SQL -
cursor.execute()(Python DB-API)、Statement.executeUpdate()(JDBC)未用占位符,而是拼接表名或ORDER BY - MyBatis 的
${}(非#{})——XML 里写错一个符号,整条语句就裸奔
为什么表名、字段名、ORDER BY 无法参数化
数据库协议在解析阶段就必须确定结构,驱动层根本不支持运行时替换标识符。这意味着:
-
SELECT * FROM {table_name}中的{table_name}不可能靠params=补救 -
User.objects.order_by(request.GET.get('sort'))若未校验,攻击者可传id; DROP TABLE users-- -
WHERE status IN (" + ",".join(status_list) + ")"即便每个值都安全,括号内拼接仍绕过参数机制 - 真正可行的只有白名单:如
if sort_field not in ["created_at", "name", "score"]:直接拒掉
动态字段名和私有 API 的隐蔽风险
看似在用 ORM,实则已踩进元编程陷阱:
-
filter(**{field: value})中的field是键名,不会被转义——传__class__或profile__user__is_staff可能触发非预期行为或数据泄露 -
_meta.db_table、_meta.get_field()等私有属性不校验输入,也不绑定参数,常被用于日志表名拼接或字段反射 -
annotate(score=Func(..., extra={'decimals': user_input}))——extra字典值直插模板,无任何过滤 - 安全做法不是禁用,而是先白名单过滤:
if field not in ["title", "author__name"]: raise ValueError
最易被忽略的一点:ORM 安全性完全取决于你是否让 SQL 结构和值严格分离。值可以参数化,结构必须白名单或硬编码——哪怕只在一个 text() 调用里拼了一个字段名,整条查询就不再可信。











