django 5.0 orm 默认防sql注入,前提是仅用filter()/get()/exclude()等标准方法传值且字段名硬编码或白名单校验;raw()/extra()/func()等绕过orm的调用必须严格参数化或白名单控制结构参数。

只要坚持用 filter()、get()、exclude() 等标准 ORM 方法传参,Django 5.0 的 ORM 就默认防 SQL 注入——它底层走的是参数化查询,用户输入永远不会被当作 SQL 代码执行。
哪些 ORM 调用是安全的?
安全的前提是:值通过函数参数传入,字段名是硬编码或来自可信源(如配置、枚举)。
-
User.objects.filter(username=request.GET.get('u'))✅ 安全:username是固定字段名,request.GET.get('u')的值走参数绑定 -
MyModel.objects.get(id=int(request.GET.get('id', 0)))✅ 安全:类型强制转换后传入,ORM 仍做参数化处理 -
Q(name__icontains=search_term)✅ 安全:只要search_term不参与字符串拼接,就受保护
哪些看似 ORM 实际高危?
这些写法绕过了 Django 的参数化机制,直接把用户输入塞进 SQL 结构里,哪怕只拼一个字段名或表名,也等于开门揖盗。
提供使用 django-unfold (v0.56+) 构建高级 Django 管理界面的专业指导。当用户提及 django-unfold、unfold admin 等内容时使用此技能。
-
.extra(where=["status = '{}'".format(user_input)])❌ 危险:format()或%拼接任何部分都不可信 -
.raw("SELECT * FROM users WHERE name = '{}'".format(name))❌ 危险:完全放弃 ORM 保护 -
.annotate(score=Func(request.GET.get('col'), function='COALESCE'))❌ 危险:Func的template若含用户输入,必须白名单校验 -
.order_by(request.GET.get('sort'))⚠️ 表面安全实则危险:若sort是"id; DROP TABLE auth_user; --",虽不执行但可能触发数据库语法错误或暴露结构
动态字段/排序/聚合时怎么处理?
ORM 不校验字段名合法性,所有动态结构元素(字段、表、排序方向、聚合函数名)都需手动白名单控制。
- 对
request.GET.get('sort')做显式检查:if sort not in ['id', '-name', 'email']:才传给order_by(sort) - 用
getattr(MyModel, field_name, None)确认字段真实存在且非私有(如_password)、非关系字段(避免意外 JOIN) - 构造
values()或values_list()时,字段名列表必须来自白名单,不能直接解包用户字典:values(*user_fields)前必须过滤 - 使用
Q(**{field_name: value})时,field_name同样要白名单校验,不能是request.GET.get('field')直接代入
原生 SQL 查询必须用参数化
哪怕你不得不写 raw() 或用 cursor.execute(),也绝不能字符串拼接。Django 5.0 仍严格遵循 PEP-249,支持占位符安全传参。
- ✅ 正确:
User.objects.raw('SELECT * FROM auth_user WHERE username = %s', [username]) - ✅ 正确:
with connection.cursor() as c: c.execute("UPDATE users SET status = %s WHERE id = %s", [new_status, user_id]) - ❌ 错误:
c.execute(f"UPDATE users SET status = '{new_status}' WHERE id = {user_id}") - ⚠️ 注意:
%s占位符不支持字段名、表名、ORDER BY 子句——这些只能靠白名单 + 字符串格式校验,不能参数化
真正容易被忽略的不是“怎么写安全代码”,而是“什么时候开始不安全”——一旦出现 format、%、f-string 和 SQL 片段混用,哪怕只有一行,整个查询链就失效了。字段名白名单不是可选项,是动态操作的强制前置步骤。










