Django 官方同时发布了 6.0.5 和 5.2.14 两个安全修复版本,修补了三个低严重度(low)漏洞:ASGI 文件上传内存限制绕过、缓存页面下的会话固定、以及 Vary: * 导致的私有数据泄露。严重度虽低,但每一个在特定部署场景下都能造成实际损害——尤其是生产环境使用 ASGI 和缓存中间件的团队,应当尽快升级。
ASGI 上传绕过:Content-Length 缺失时的内存炸弹
CVE-2026-5766 的核心问题在于:当 ASGI 请求缺少 Content-Length 头或该值被故意压低时,Django 的 FILE_UPLOAD_MAX_MEMORY_SIZE 限制可以被绕过。结果是大文件直接被加载到内存,而非按预期写入临时文件,极端情况下可拖垮服务进程。
Django 文档一直强调:FILE_UPLOAD_MAX_MEMORY_SIZE 不是防线,真正的限制应在 Web 服务器层配置。这次漏洞恰好印证了这一点——ASGI 模式下前端服务器(如 Daphne、Uvicorn)对缺失 Content-Length 的请求处理方式与 WSGI 不同,Django 内部的内存阈值判断依赖了这个头信息。
实际应对:
升级到 6.0.5 或 5.2.14 是根本解法。同时,在反向代理层加一道硬限制是更稳妥的做法。以 Nginx 为例:
server {
# 限制请求体大小为 10MB,超出直接返回 413
client_max_body_size 10m;
location / {
proxy_pass http://127.0.0.1:8000;
# 确保转发时保留 Content-Length
proxy_set_header Content-Length $content_length;
}
}
如果你用 Uvicorn 直接对外暴露,也可以在启动参数中限制:
# 限制 Uvicorn 最大请求体为 10MB(需配合自定义中间件)
uvicorn myproject.asgi:application --limit-request-line 8190 --limit-request-field_size 8190
注意:Uvicorn 本身没有直接的
client_max_body_size等价参数,生产环境应始终在前方放置 Nginx 或类似反向代理来做第一道过滤。
缓存 + SESSION_SAVE_EVERY_REQUEST:会话固定攻击路径
CVE-2026-35192 描述了一个精巧的攻击链:
- 管理员开启了
SESSION_SAVE_EVERY_REQUEST = True(每次请求都保存会话,哪怕未修改)。 - 用户访问了一个公开的、可被缓存的页面。
- 由于会话未被修改,Django 的响应头不会在
Vary中包含Cookie——缓存中间件认为这个响应与 Cookie 无关,可以缓存。 - 攻击者获取到这个缓存副本后,可以将其中的会话 ID "固定"到受害者身上,实现会话劫持。
问题出在 Vary 头的逻辑:当会话未修改时,Django 认为响应内容不依赖会话状态,因此不在 Vary 中声明 Cookie。但 SESSION_SAVE_EVERY_REQUEST = True 意味着 Cookie 中的 sessionid 实际上每次都在变化(或至少被重新发送),缓存层却不知道这一点。
排查你的配置:
# settings.py — 检查这两项是否同时存在
SESSION_SAVE_EVERY_REQUEST = True # ← 风险开关
# 并且使用了 django.middleware.cache.UpdateCacheMiddleware
MIDDLEWARE = [
'django.middleware.cache.UpdateCacheMiddleware', # ← 缓存写入
# ... 其他中间件 ...
'django.middleware.cache.FetchFromCacheMiddleware', # ← 缓存读取
]
如果两者同时存在,升级是必须的。升级后,Django 会在 SESSION_SAVE_EVERY_REQUEST = True 时正确地将 Cookie 加入 Vary 头,阻止缓存中间件缓存带有会话信息的响应。
临时缓解方案(升级前):将公开页面视图函数加上 @never_cache 装饰器,或关闭 SESSION_SAVE_EVERY_REQUEST。
from django.views.decorators.cache import never_cache
@never_cache
def public_homepage(request):
"""公开首页,但不允许被缓存中间件存储"""
return render(request, 'home.html')
Vary: * 处理失误:私有数据被缓存并分发
CVE-2026-6907 涉及 UpdateCacheMiddleware 对 Vary: * 头的处理。按 HTTP 规范,Vary: * 意味着响应内容依赖于请求中的所有头字段,本质上等于"不要缓存"。但 Django 的缓存中间件此前没有正确识别这个标记,仍然将响应存入缓存——如果这个响应包含用户私有数据(如个人信息页面),后续不同用户的请求可能从缓存中拿到前一个用户的数据。
触发场景示例:
# 视图中显式设置 Vary: *,意图是"完全不可缓存"
from django.views.decorators.vary import vary_on_headers
@vary_on_headers('*') # 这会生成 Vary: * 响应头
def user_profile(request):
"""用户个人资料页——绝对不应被缓存"""
return render(request, 'profile.html', {
'user': request.user,
'email': request.user.email, # ← 私有数据
})
升级后,UpdateCacheMiddleware 遇到 Vary: * 会跳过缓存写入,与 HTTP 规范语义一致。
升级操作清单
三个漏洞的补丁已合入 main、6.0、5.2 三个分支。升级步骤:
# 升级 Django(选择对应版本)
pip install --upgrade django==6.0.5
# 或
pip install --upgrade django==5.2.14
# 升级后验证版本
python -m django --version
# 检查校验和(可选但推荐)
# 下载 checksums 文件后比对:
curl -O https://media.djangoproject.com/pgp/django-6.0.5.checksum.txt
sha256sum django-6.0.5.tar.gz
# 验证 PGP 签名(发布者:Sarah Boyce, Key ID 3955B19851EA96EF)
gpg --verify django-6.0.5.tar.gz.asc django-6.0.5.tar.gz
升级完成后,逐项确认:
- ASGI 部署:确认反向代理已配置
client_max_body_size,不再单纯依赖 Django 的内存限制。 - 缓存中间件:确认
SESSION_SAVE_EVERY_REQUEST与缓存中间件共存时,响应头中Vary包含Cookie。 - Vary: * 规范:确认设置了
Vary: *的视图不再被UpdateCacheMiddleware缓存。
三个漏洞严重度都是 low,但这不意味着可以拖延——低严重度指的是攻击条件较苛刻,而非影响轻微。ASGI 部署下的内存炸弹、缓存层的会话固定和私有数据泄露,一旦触发,后果都不低。升级到 6.0.5 或 5.2.14,几分钟的事,值得今天就做。