django orm仅防“值”层面sql注入,字段名、表名、order by等结构参数需白名单校验;filter(**{field_name: value})动态传键名不转义,易遭__class__或子查询攻击;raw()/extra()必须用params传参,禁字符串拼接;rawsql需手动参数化,优先用orm原生表达式。

Django ORM 默认能防 SQL 注入,但仅限于“值”层面——字段名、表名、ORDER BY、GROUP BY、函数名这些结构部分,它不管。
filter() 里动态传字段名会直接绕过防护
常见错误是把用户输入的 field_name 直接解包进 filter():Article.objects.filter(**{field_name: value})。Django 不会对字典的 key 做任何转义,攻击者传 __class__ 或 id__in 配合子查询就能触发非预期行为。
- 必须用白名单校验字段名,例如:
if field not in ["title", "author__name", "created_at__year"]:再抛异常 - 替代方案:用
Q对象组合条件,或提前定义字段映射表(如{"search_title": "title__icontains"}) -
order_by()、values()同样接收字符串字段名,若来源不可信,也得走白名单
raw() 和 extra() 是高危接口,拼接即失守
raw() 和 extra() 完全跳过 ORM 编译器,生成的 SQL 字符串由你全权负责。哪怕只拼一个 WHERE name = '{user_input}',就等于把数据库大门钥匙交出去。
- 正确写法是严格使用
params=参数传值:Person.objects.raw("SELECT * FROM myapp_person WHERE name = %s", params=[name]) -
params必须是列表或元组;PostgreSQL 支持命名占位符%(name)s,但 MySQL / SQLite 不支持,别混用 -
extra(where=["status = '{}'".format(user_input)])这种写法和裸写 SQL 没区别,必须改用extra(where=["status = %s"], params=[user_input])
RawSQL 和 annotate() 混用用户输入需手动参数化
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}%"]) - 更推荐优先用 ORM 原生表达式:
Case(When(title__icontains=search, then=1), output_field=IntegerField()) - 注意占位符顺序必须和
params列表索引一致,位置错一位就可能查错数据
最常被忽略的是:ORM 的安全机制只覆盖“值”,不覆盖 SQL 结构。字段名、排序字段、聚合函数名、全文检索配置(如 __search)、正则表达式(__regex)里的模式串——这些若来自用户且未经清理,参数化也救不了。白名单不是可选项,是结构动态化的必经关卡。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











