Django ORM 4.2 默认参数化查询仅防护值,不防护结构;动态字段名、raw()/extra() 字符串拼接、RawSQL 拼接均需白名单校验或严格参数化。

Django ORM 在 4.2 版本下对 filter()、get()、create() 等常规方法默认启用参数化查询,只要不绕过它,SQL 注入风险极低——但三个地方容易被当成“安全区”而实际翻车。
动态字段名传入 filter() 会绕过参数化
字段名(比如 title__icontains 中的 title)若来自用户输入,filter(**{field_name: value}) 就不再安全。Django 不会对键名做转义,攻击者可传入 __class__、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对象组合条件,或提前定义字段映射表(如{"search_title": "title__icontains"})
raw() 和 extra() 必须显式用 params= 传参
raw() 和 extra() 完全跳过 ORM 编译器,直接生成 SQL。字符串拼接哪怕只加一个单引号,就等于敞开大门。
- 危险示例:
Person.objects.raw(f"SELECT * FROM myapp_person WHERE name = '{name}'") - 正确写法:
Person.objects.raw("SELECT * FROM myapp_person WHERE name = %s", params=[name]) - 注意:
params必须是列表或元组;extra(where=["..."])中的字符串不能含用户输入,否则需先白名单过滤
RawSQL 与 annotate() 混用时 params 不自动覆盖整个表达式
RawSQL 本身不参数化,把用户输入直接塞进 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())
最常被忽略的一点:ORM 的参数化只保护“值”,不保护“结构”。字段名、表名、ORDER BY 子句、GROUP BY 字段——这些一旦由用户控制且未经校验,再严格的 params 也救不了。


















