Django ORM仅对filter()/get()等值查询天然防注入;动态字段名须白名单校验,raw()/extra()必须用params列表传参且只认%s,RawSQL需手动参数化LIKE,结构参数如ORDER BY、表名等必须白名单控制。

Django ORM 本身对 filter()、get()、exclude() 等常规查询天然免疫 SQL 注入,但只要用到 raw()、extra()、RawSQL 或动态字段名,防护就立刻失效——这些地方不是“可能出问题”,而是“必然出问题”,除非你严格按规则操作。
filter() 动态字段名必须白名单校验
很多人把 request.GET.get('field') 直接塞进 filter(**{field_name: value}),以为 ORM 会自动过滤。它不会。字段名不走参数化,攻击者传 is_staff__gt、_meta.model_name 或 password__contains 就能触发越权或信息泄露。
- 必须先校验:
if field_name not in ["title", "author__name", "status"] - 再确认字段真实存在:
getattr(MyModel, field_name, None)且不是property - 别用
Q("username = '{}'".format(u))拼字符串;改用Q(**{field_name + '__icontains': u})
raw() 和 extra() 中的 %s 不等于安全
raw() 是唯一带模型映射的原生 SQL 入口,但它只认位置占位符 %s,不支持 {} 或 %(name)s(SQLite/MySQL 不认命名参数)。而 extra() 的 params 参数被 Django 忽略——写 extra(where=["status = %s"], params=[u]) 毫无作用,%s 被当作文本字面量处理。
-
raw()安全写法:User.objects.raw("SELECT * FROM auth_user WHERE username = %s", params=[username]),params必须是列表或元组 -
extra()的where、tables、select字符串里禁止含任何用户输入 - 真要动态条件,优先用
Q对象组合:Q(username__icontains=u) | Q(email__endswith='@example.com')
RawSQL 在 annotate() 中必须手动参数化 LIKE
RawSQL 表达式用于 annotate() 或 filter() 时,params 只绑定表达式内部的占位符,不会覆盖整个 SQL 字符串。漏掉转义,% 和 _ 就变成通配符,造成逻辑漏洞甚至拖垮数据库。
立即学习“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}%"]) - 更稳妥:
Case(When(title__icontains=search, then=1), output_field=IntegerField()),ORM 原生表达式自动处理转义和索引优化 - 若必须用原生
LIKE,调用connection.ops.prep_for_like_query(search)手动转义
最常被忽略的是结构参数:字段名、表名、ORDER BY、GROUP BY、函数名——这些都不走参数绑定,params= 粘不住。哪怕你 100% 没拼过 SQL 字符串,只要 order_by(request.GET.get('o')) 没白名单,就可能被用来触发全表扫描或索引失效。


















