Django ORM仅防护“值”层面SQL注入,动态字段名需白名单校验;raw()/extra()必须用params传参且只认%s;RawSQL需手动参数化;cursor.execute()须严格参数化,结构化参数如表名、排序字段等必须白名单控制。

Django ORM 默认对用户输入的“值”做参数化处理,filter()、get()、exclude() 等方法基本不用额外防注入;但只要出现 raw()、extra()、cursor.execute() 或动态字段名,防护就立刻失效——这不是配置问题,是代码写法问题。
filter() 动态字段名为什么危险
很多人以为 filter(**{field_name: value}) 和普通 filter(title=value) 一样安全,其实不然。Django 不会对字典 key 做任何转义或校验,攻击者传 __class__、id__in 甚至子查询字符串,都能直接触发非预期行为。
- 错误示例:
field = request.GET.get("sort"); Article.objects.filter(**{field: "python"}) - 必须白名单校验字段名,比如只允许
["title", "author__name", "created_at__year"] - 更稳妥的替代:用
Q对象组合条件,或提前定义映射表(如{"q": "title__icontains"}) -
order_by()、values()、annotate()里传的字段名同样要走白名单
raw() 和 extra() 必须严格用 params=
raw() 和 extra() 完全跳过 ORM 编译器,SQL 字符串由你全权负责。哪怕只拼一个单引号,就等于把数据库钥匙交出去。
-
raw()只支持位置占位符%s,params必须是列表或元组,不能是字典(除非只跑 PostgreSQL 且确认驱动支持) - 错误:
User.objects.raw(f"SELECT * FROM auth_user WHERE username = '{username}'") - 正确:
User.objects.raw("SELECT * FROM auth_user WHERE username = %s", params=[username]) -
extra(where=["status = %s"], params=[user_status])是唯一安全路径;extra(where=[f"status = '{user_status}'"])和裸写 SQL 没区别
RawSQL 表达式里混用户输入怎么防
RawSQL 用在 annotate() 或 filter() 中时,它不继承外层 QuerySet 的参数化机制,params= 只绑定它自己 SQL 内的占位符。
图片提示词生成器?不止如此。 马甲系统 —— 把脑海中的画面,翻译成AI能理解的专业表达。 用得越多,它越懂你:首次需要多问几句确认方向,用久了几乎一说就懂。 用得越多,它越快:缓存机制让后续对话越来越省。 RAG进化:成功案例持续入库,越跑越聪明。 输入「新手指南」查看完整功能介绍
立即学习“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}%"]) - LIKE 模糊匹配还要注意通配符:
%和_是数据库语法,不是注入,但属逻辑漏洞;优先用__icontains替代 - 更推荐 ORM 原生表达式:
Case(When(title__icontains=search, then=1), output_field=IntegerField())
cursor.execute() 没有自动防护
绕过 ORM 直接用 connection.cursor(),Django 不提供任何防护,完全依赖底层驱动的参数化能力。
- 必须用
cursor.execute("SELECT * FROM user WHERE id = %s", [user_id]) - 禁止
cursor.execute(f"SELECT * FROM user WHERE id = {user_id}")或"%s" % user_id - 别试图用
connection.ops.adapt_unknown_value()手动转义——难维护、易漏、不跨数据库 - 如果必须动态拼表名或字段名,只能靠白名单,不能靠转义
最常被忽略的是:ORM 的安全机制只覆盖“值”,不覆盖 SQL 结构。字段名、表名、ORDER BY、GROUP BY、函数名、正则模式(__regex)、全文检索配置(__search)——这些若来自用户输入且未经校验,再严格的 params 也救不了。

















