django 5.0 orm 不提供自动 sql 注入拦截机制,仅通过参数化查询保障值的安全;字段名、表名等结构化部分需开发者白名单校验,raw() 的 params 仅对 %s 有效,extra() 的 where 忽略 params。

Django 5.0 的 ORM 本身不提供“自动化拦截 SQL 注入”的运行时钩子或中间件机制——它只在你**正确使用其公开 API**时,通过参数化查询天然规避注入;一旦绕过标准路径(比如拼字符串、塞进 raw() 或 extra()),就完全失去保护。
filter() 和 get() 为什么看起来“自动防注入”
Django 不是在执行前扫描 SQL 字符串去“拦截”恶意内容,而是根本不会生成带用户输入的 SQL 字符串。调用 User.objects.filter(username=request.GET.get('u')) 时,ORM 构建的是表达式树,最终交由数据库驱动以 cursor.execute("SELECT ... WHERE username = %s", [u]) 方式执行。值永远走参数绑定,字段名则必须是合法 Python 标识符。
- 安全前提是:字段名写死,或来自严格白名单(如
if field in ['name', 'email']) - 危险操作:
filter(**{request.GET.get('f'): value})—— 键名不经过任何转义,f=__class__就能触发模型元信息泄露 - Django 5.0 没新增“自动校验字段名”逻辑,该行为与 4.x 一致
raw() 和 extra() 中的 params 不是万能保险
raw() 支持 params,但仅对位置占位符 %s 生效;extra() 的 where 参数压根不读取 params,传了也忽略——这是最容易误判的点。
- ✅ 正确:
User.objects.raw("SELECT * FROM auth_user WHERE email = %s", params=[email]) - ❌ 危险:
.extra(where=["email = '{}'".format(email)])——params在这里完全无效 - ❌ 更危险:
.extra(select={'score': "COALESCE({}, 0)".format(request.GET.get('col'))})—— 字段名被当 SQL 片段执行
order_by()、values() 等结构化参数必须手动白名单
ORM 对 order_by('id') 这类调用不做任何校验,因为字段名属于 SQL 结构而非值。Django 5.0 仍要求你自行限制范围,否则 order_by('id; DROP TABLE auth_user --') 虽然语法错误,但 order_by('password__contains') 可能暴露敏感字段逻辑。
- 必须检查:
sort = request.GET.get('o'); if sort not in ['id', '-name', 'email']: raise ValueError - 更稳妥:用
getattr(User, sort.lstrip('-'), None)确认字段真实存在且非property -
values(request.GET.get('fields'))同理,字段名未校验 = 查询结果可控面扩大
真正容易被忽略的,是“结构即攻击面”——Django 5.0 的 ORM 安全边界只划在“值”的参数化上,字段名、表名、函数名、排序方向、聚合别名,全都不受保护。你没法靠升级到 5.0 就自动获得这些校验,它们得写在你自己的视图或管理器里。











