FlawOpen

flawopen.com/Python 中的 SQL Injection/Django ORM

Django ORM 默认能防止 SQL Injection 吗?

参考页面
简短回答

对于 .filter()、.get() 和 .exclude() 等常见查询,Django QuerySet 会将 SQL 代码与值分开,并把值作为参数交给数据库驱动。这种保护并不覆盖所有查询构造方式:raw SQL、自定义表达式以及由用户控制的查询结构都需要单独审查。

在 Django QuerySet 中安全传递值

不安全 — 将输入插值到原生 SQL
username = request.GET.get("u", "")
User.objects.raw(
    f"SELECT * FROM myapp_user WHERE username = '{username}'"
)
安全 — 通过 raw() 的 params 绑定输入
username = request.GET.get("u", "")
User.objects.raw(
    "SELECT * FROM myapp_user WHERE username = %s",
    [username],
)

保护机制需要特别审查的地方

Model.objects.raw()

Manager.raw() 会执行提供的 SQL。将用户可控值通过 params 传入,并且不要给占位符加引号,例如 User.objects.raw("SELECT * FROM users WHERE username = %s", [username])。不要用 f-string、% 格式化或字符串拼接把值写进 SQL。参数表示值,不能表示表名、列名等 SQL 标识符。

直接使用 connection.cursor()

直接访问数据库时,使用 cursor.execute(sql, params),并按照 Django 文档的占位符格式单独传值。不要预先把值插入 sql。还要审查 RawSQL 和 Func 表达式:模板上下文和命名参数可能被插入 SQL,而不是作为参数绑定。

由用户控制的字段路径

.filter(**{field: value}) 中的用户字段路径并不自动构成 SQL Injection:Django 会把 lookup 名称解析为查询结构。但它可能暴露 endpoint 不应提供的字段、关系或 lookup,也可能导致错误。将外部排序和筛选选项映射到严格允许的模型字段与 lookup 列表。

如何审查 Django 查询

rg -n '(\.raw\(|RawSQL\(|\.extra\(|connection\.cursor\()' --glob '*.py' .
搜索 .raw()、RawSQL、.extra() 和 connection.cursor() 的使用。逐项审查并追踪输入值:确认使用参数绑定,检查不可信 SQL 片段、动态标识符或 lookup,以及自定义 Func 模板。

常见问题

extra() 安全吗?

QuerySet.extra() 是用于添加 raw SQL 片段的旧 API。新代码应避免使用,并仔细审查现有的 where 和 select 参数。优先使用 QuerySet 表达式;必须使用 raw SQL 时,通过文档规定的 params 参数绑定值。

Django REST Framework 也适用吗?

Django REST Framework 不会改变 ORM 的 SQL 参数化行为。来自 views、serializers 和 viewsets 的查询都遵循相同规则;客户端控制的排序和筛选字段也应通过允许列表限制。

参考资料

另请参阅 SQL 注入(CWE-89):原理与各编程语言的防护方法 — flawopen.com Doctrine ORM SQL 注入:安全查询实践 EF Core FromSqlRaw:安全参数化 SQL FastAPI SQL 注入:Pydantic 能防什么,不能防什么 GORM Raw SQL:防止 Go 中的 SQL 注入 转义引号足以防止 SQL Injection 吗? 使用 ORM 足以防止 SQL 注入吗?