Django凭借其“全功能”特性和成熟的对象关系映射(ORM)系统,从初创企业的SaaS后端到财富500强级平台均有应用。而CVE-2025-64459漏洞正是出现在ORM层:当内部查询控制参数泄露到查询构造过程中时,会触发SQL注入漏洞。具体而言,若应用程序将用户可控的字典展开传递至QuerySet.filter()、exclude()、get()或Q()调用时,攻击者可通过注入_connector关键字改变ORM向数据库发送的逻辑语句。在实际应用中,这种漏洞常见于未认证接口的通用筛选模块,可能导致数据窃取和权限绕过等严重后果。Django官方根据安全政策将其评定为高危漏洞,并于2025年11月5日发布修复版本。
在正常条件下,Django ORM 会以高度受控的方式将关键字参数转换为 SQL:
# Intended, safe-style usage
User.objects.filter(
is_active=True,
email__icontains="example.com",
)
每个关键字都对应一个字段查询。Django 会负责对值进行转义,并构建如下的 WHERE 子句:
WHERE "user"."is_active" = TRUE
AND "user"."email" ILIKE '%example.com%'
针对更复杂的逻辑,开发人员会使用Q对象来构建布尔表达式树:
from django.db.models import Q
qs = User.objects.filter(
Q(is_active=True) & (Q(role="admin") | Q(role="staff"))
)
在内部实现中,Q对象维护着一个子节点列表和一个连接器,该连接器指示Django使用AND、OR还是XOR来连接这些子节点。此外还存在一个_negated标志位,用于对子树进行逻辑取反。
这些控制参数——_connector 与 _negated——属于内部调控机制,不应由终端用户操控。
当应用程序同时涉及以下两种操作时,问题便会显现:
用户输入数据的字典展开
Django允许将 _connector(及 _negated)作为关键字参数传入 Q 对象和 QuerySet 方法
在实际的Django应用开发中,"动态筛选"是一种非常常见的编程模式:
# Risky pattern if request.GET is not validated
def search_users(request):
filters = request.GET.dict() # user-controlled
users = User.objects.filter(**filters)
return users
若用户提交如下请求:
[email protected]&_connector=...
那么在补丁发布前,Django会接受_connector作为查询树内部条件组合方式的控制参数。这意味着用户输入不再仅仅是数据——它开始能够影响生成的SQL语句结构。
即使直接通过原始字典构建Q对象,同样会触发此问题:
from django.db.models import Q
def post_list(request):
query_params = dict(request.GET.items()) # user-controlled
q_filter = Q(**query_params) # also dangerous
posts = Post.objects.filter(q_filter)
...
通过精心构造的_connector值,攻击者能够强制查询树中的部分节点以异常方式连接,甚至在某些情况下将SQL代码片段注入连接器本身。
Django的修复方案在两个层面实施:
QuerySet 层级验证
在 django/db/models/query.py 中,Django 现在维护了一个被禁关键字参数集合,并会提前拦截这些参数:
PROHIBITED_FILTER_KWARGS = frozenset(["_connector", "_negated"])
def _filter_or_exclude_inplace(self, negate, args, kwargs):
if invalid_kwargs := PR