Django 5.0 ORM 仅对查询值防SQL注入,字段名、排序、聚合、子查询及原生SQL片段均需手动校验;filter动态键名须白名单限制,raw/extra必须用%s+params,RawSQL中LIKE需手动转义,order_by/values等字段名同样不可信。

Django 5.0 的 ORM 本身对「值」层面的 SQL 注入免疫,但只要涉及字段名、排序、聚合函数、子查询结构或原生 SQL 片段,就立刻脱离防护范围——这类注入不报错、不抛异常,却可能泄露敏感字段、绕过权限、拖垮数据库。
filter() 动态键名必须白名单校验
Django 不会对 filter(**{field_name: value}) 中的 field_name 做任何转义或合法性检查。攻击者传 is_superuser__gt、_meta.model_name 或 password__isnull 就能触发非预期查询逻辑。
- 错误写法:
Article.objects.filter(**{request.GET.get('field'): request.GET.get('q')}) - 正确做法:先查白名单,再确认字段真实存在:
allowed_fields = {"title": "title__icontains", "author": "author__name__icontains"}<br>field = request.GET.get('field')<br>if field not in allowed_fields:<br> raise Http404("Invalid field")<br>query = {allowed_fields[field]: request.GET.get('q')}<br>Article.objects.filter(**query) - 别用
getattr(model, field_name, None)替代白名单——property、自定义方法、私有字段都可能被误判为合法
raw() 和 extra() 必须严格用 %s + params 列表
raw() 和 extra() 完全跳过 ORM 编译器,SQL 字符串由你全权负责。哪怕只拼一个单引号,就等于把数据库钥匙交出去。
-
raw()正确用法:User.objects.raw("SELECT * FROM auth_user WHERE username = %s", params=[username])——params必须是列表或元组,且仅支持位置占位符%s(MySQL/SQLite/PostgreSQL 都兼容) -
extra()的where参数里不能含用户输入;params在extra()中被 Django 忽略,所以extra(where=["status = %s"], params=[u])是无效的 - 危险写法示例:
User.objects.extra(where=["username = '" + u + "'"])、raw(f"SELECT * FROM user WHERE name LIKE '%{s}%'")
RawSQL + annotate() 中的 LIKE 模式需手动转义
RawSQL 只对 params 对应的占位符做参数绑定,不会自动处理字符串内的通配符或 SQL 元字符。漏掉转义,% 和 _ 就会变成模糊匹配符号,导致结果集爆炸或慢查询。
- 错误:
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()、values()、annotate() 的字段名同样不可信
这些方法接收的字段名字符串,和 filter() 的键名一样,不走参数化流程。用户控制 order_by(request.GET.get('sort')) 可能按 __class__ 或 user__profile__phone 排序,暴露关联数据或触发 N+1 查询。
- 必须校验:
if sort_field not in ["created_at", "-title", "author__name"]:再抛异常 - 注意连字符:
-created_at是合法排序字段,但sort_field.startswith('-')后要取sort_field[1:]再校验基础字段名 -
values('user__password')这类写法虽不会报错,但会把密码字段拖出来——字段白名单得覆盖所有可能路径
最常被忽略的是:ORM 的安全边界非常清晰——它只保护“值”,不保护“结构”。哪怕你一行原生 SQL 都没写,只要把用户输入直接塞进 order_by、values、annotate、extra 或动态 filter 键名里,就已站在漏洞边缘。


















