Django ORM 参数化仅防护SQL值而非结构,动态字段名、raw()/extra()字符串拼接、RawSQL未参数化三处易致注入;须白名单校验字段、params传参、优先用ORM原生表达式。

Django ORM 默认对 filter()、get()、create() 等常规方法做参数化处理,只要不绕过它,SQL 注入风险极低——但三个地方容易被当成“安全区”而实际翻车。
filter() 里用动态字段名会触发注入
当字段名(比如 name__contains 中的 name)来自用户输入时,filter(**{field_name: value}) 就不再安全。Django 不会对键名做转义,攻击者可传入 __class__、__dict__ 或拼接 id__in + 子查询等构造非法路径。
- 错误写法:
field = request.GET.get("field"); Article.objects.filter(**{field: value}) - 安全做法:白名单校验字段名,例如
if field not in ["title", "author__name", "created_at__year"]: raise ValueError("Invalid field") - 替代方案:用
Q对象组合条件,或提前定义好字段映射表
raw() 和 extra() 必须用 params= 传参,不能字符串拼接
raw() 和 extra() 完全跳过 ORM 编译器,直接生成 SQL。哪怕只拼一个 WHERE name = "' + user_input + '",就等于敞开大门。
- 危险示例:
Person.objects.raw(f"SELECT * FROM myapp_person WHERE name = '{name}'") - 正确写法:
Person.objects.raw("SELECT * FROM myapp_person WHERE name = %s", params=[name]) - 注意:
params必须是列表或元组,不能是字典(除非后端明确支持命名占位符,如 PostgreSQL 的%(name)s) -
extra(tables=["..."])或extra(where=["..."])同样危险,where里的字符串不能含用户输入
RawSQL 和 annotate() 中混用用户输入需手动防护
RawSQL 表达式用于 annotate() 或 filter() 内部,它本身不自动参数化。如果把用户输入直接塞进 SQL 字符串,params= 参数不会覆盖整个表达式。
立即学习“Python免费学习笔记(深入)”;
- 错误写法:
annotate(score=RawSQL(f"CASE WHEN title LIKE '%{search}%' THEN 1 ELSE 0 END")) - 正确写法:
RawSQL("CASE WHEN title LIKE %s THEN 1 ELSE 0 END", params=[f"%{search}%"]) - 更稳妥:优先用 ORM 原生表达式,比如
Case(When(title__icontains=search, then=1), output_field=IntegerField()) - 注意:
RawSQL的params是位置参数,顺序必须和 SQL 中占位符一致
最常被忽略的一点:ORM 安全性只覆盖“值”,不覆盖“结构”。字段名、表名、函数名、ORDER BY 子句、GROUP BY 字段——这些都属于 SQL 结构,一旦由用户控制且未经校验,参数化也救不了。别把 params= 当万能胶布,它只粘得住值,粘不住语法骨架。


















