django 4.2+ 的 filter()/get()/exclude() 本身安全,但动态字段名需白名单校验;raw() 必须用 %s 占位符配列表 params;extra() 的 where 不支持 params,应改用 q 对象组合条件。

只要不手动拼SQL、不滥用 raw()/extra()/Func(),Django 4.2+ 的 filter()、get()、exclude() 本身不会引入SQL注入——但绝大多数漏洞恰恰出在“以为安全”的地方。
filter() 动态字段名必须白名单校验
很多人用 filter(**{field_name: value}) 实现动态查询,却忽略了 field_name 若来自用户请求(如 request.GET.get('field')),攻击者可传入 is_staff__gt、_meta.model_name 甚至 password__contains 等非法字段,造成越权或信息泄露。
- 必须提前校验:
if field_name not in ['name', 'email', 'status']: - 进一步确认字段真实存在且可查:
getattr(User, field_name, None) and not isinstance(getattr(User, field_name), property) - 别信
Q("username = '{}'".format(u))——这是字符串拼接,不是ORM查询
raw() 调用只认 %s 占位符,且 params 必须是 list 或 tuple
raw() 不解析SQL结构,只靠 params 做参数化。用 .format()、f-string 或单引号包裹 %s 都会失效,变成直接拼接。
- ✅ 安全写法:
User.objects.raw("SELECT * FROM auth_user WHERE username = %s", params=[username]) - ❌ 危险写法:
User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{username}'") - ❌ 同样危险:
"WHERE username = '%s'".format(username)或params=username(必须是[username]) - ⚠️ 注意:SQLite 下不支持
%(name)s,只认位置占位符%s
extra() 的 where 参数根本不接受 params
extra(where=["status = %s"], params=[u]) 是无效的——Django 会忽略 params,把 %s 当字面量处理,最终生成 WHERE status = %s,而非带值的条件。
- 真要动态条件,优先改用
Q+filter(),比如Q(**{field + '__icontains': value}) - 避免在
extra(select={})的 SQL 字符串里插变量:"COALESCE({}, 0)".format(request.GET.get('col'))是高危操作 - 如果非用
extra(),所有变量都得走白名单 + 硬编码,不能来自用户输入
order_by() 和 annotate() 中的关联字段需防二阶注入
CVE-2021-35042 表明,order_by() 在 Django 3.1.x __ 关联字段解析不严,可被用于二阶注入;而 annotate() 配合 RawSQL 或 Func 模板时,若模板字符串含用户输入,同样危险。
- 升级到 Django ≥ 4.2 是基础,但不够——仍需校验
order_by参数是否在允许列表中(如['name', '-created_at']) -
RawSQL必须显式指定output_field,且所有参数必须走params:RawSQL("SELECT COUNT(*) FROM logs WHERE user_id = %s", params=[user_id], output_field=IntegerField()) -
Func的template里禁止插变量,否则等同于裸写SQL
最常被忽略的点是:LIKE 查询中的 % 和 _ 不属于SQL注入,但 Django 不自动转义它们——用户搜 %admin% 可能拖垮数据库。用 __icontains 替代原生 LIKE,它内部会加 ESCAPE 并转义通配符。











