Django 官方同时发布了 6.0.6 和 5.2.15 两个安全版本,修补了五个 CVE。虽然全部被评定为"低"严重度,但其中三个缓存相关漏洞可能导致私有数据被意外暴露给其他用户,在实际生产环境中不容忽视。以下逐一拆解每个漏洞的成因、影响范围和升级要点。
签名 Cookie 的盐值碰撞(CVE-2026-6873)
get_signed_cookie() 生成签名盐值的方式是把 cookie 名(key)和 salt 参数直接拼接。问题在于,不同的 (name, salt) 组合可能拼出相同的字符串——比如 ("session", "user") 和 ("sessio", "nuser") 拼接结果完全一致。这意味着在一个上下文中签发的 cookie,可能被另一个本不该接受它的上下文验证通过。
修复后,盐值推导方式改为无歧义的格式,新旧签名的 cookie 在 Django 7.0 之前仍会被兼容接受,之后旧格式将不再认可。
日常开发中,如果你在不同视图里用相同的 key 但不同的 salt 来区分 cookie 上下文,这个漏洞对你有直接影响:
# 视图 A:为管理员签发 cookie
request.get_signed_cookie("panel", salt="admin")
# 视图 B:为普通用户签发 cookie
request.get_signed_cookie("panel", salt="viewer")
# 旧版本中,如果拼接结果碰撞,视图 A 的 cookie 可能被视图 B 接受
# 升级后盐值推导不再依赖简单拼接,碰撞风险消除
升级后无需改动业务代码,但建议在 Django 7.0 发布前完成全量 cookie 签名格式的过渡。
SMTP 连接的 STARTTLS 失败后明文发送(CVE-2026-7666)
当 EMAIL_USE_TLS=True 时,Django 的 SMTP 后端会尝试 STARTTLS 协商。如果握手失败,连接并未被正确关闭,而是以半初始化状态留在连接池中。后续邮件发送会复用这条未加密的连接——邮件内容以明文传输。
fail_silently=True 的场景更容易触发这个问题,而 send_mail() 和 BrokenLinkEmailsMiddleware 默认就是 fail_silently=True。使用 EMAIL_USE_SSL=True(直接 SSL 连接,而非 STARTTLS 协商)的配置不受影响。
如果你的项目用 STARTTLS 发邮件,最直接的缓解方式是切换到 SSL:
# settings.py — 从 STARTTLS 切换到 SMTP over SSL
# 旧配置(受影响)
# EMAIL_USE_TLS = True
# EMAIL_PORT = 587
# 新配置(不受影响)
EMAIL_USE_SSL = True
EMAIL_PORT = 465
EMAIL_HOST = "smtp.example.com"
EMAIL_HOST_USER = "your_user"
EMAIL_HOST_PASSWORD = "your_password"
切换端口是必要的——SMTP SSL 通常走 465,STARTTLS 走 587。升级到 6.0.6 / 5.2.15 后 STARTTLS 路径也已修复,但 SSL 方式本身更稳健,值得长期采用。
三个缓存漏洞:UpdateCacheMiddleware 的私有数据泄漏
这三个 CVE 都指向同一个核心问题——UpdateCacheMiddleware 和 cache_page 装饰器在某些边界条件下,把本不该缓存的响应存进了缓存,导致不同用户可能看到彼此的私有数据。
大小写敏感的 Cache-Control 指令(CVE-2026-8404)
HTTP 规范中 Cache-Control 指令是大小写不敏感的,private 和 Private 含义相同。但 UpdateCacheMiddleware 只匹配小写的 private,遇到混合大小写(如 Private)就跳过了这个指令,把响应照常缓存。
注意:cache_control 装饰器和 patch_cache_control() 函数会自动将指令规范化为小写,所以只有手动设置 Cache-Control 头的响应才会出问题。
缺少 Vary: Authorization(CVE-2026-35193)
携带 Authorization 头的请求,其响应理应因认证身份不同而区分。但 UpdateCacheMiddleware 没有自动给这类响应加上 Vary: Authorization,导致不同认证用户的响应可能共享同一个缓存键——你登录后看到的页面,可能被缓存后直接返回给另一个用户。
只有当响应没有显式标记 Cache-Control: public 时才会触发此问题。
Vary 头中的空白字符(CVE-2026-48587)
Vary: * 表示响应不得被任何缓存存储。但如果头值带有尾部空格(如 Vary: *),has_vary_header() 在匹配时没有去除空白,导致通配符未被识别,响应被错误缓存。
这三个漏洞的共同教训是:不要依赖中间件自动推断缓存策略的边界情况。如果你有需要严格隔离用户的视图,显式使用 cache_control 装饰器是最安全的做法:
from django.views.decorators.cache import cache_control
@cache_control(private=True, max_age=0)
def user_profile(request):
"""私有页面——装饰器自动将 private 规范化为小写,
并确保 Vary 头正确设置,不受上述三个缓存漏洞影响。"""
return render(request, "profile.html", {
"user": request.user,
})
升级后中间件已修复这些边界情况,但显式声明缓存策略仍然是更可靠的做法。
升级操作
所有受影响的分支(main、6.1 alpha、6.0、5.2)都已打上补丁。升级只需更新 Django 版本:
# Django 6.0 系列
pip install --upgrade Django==6.0.6
# Django 5.2 系列
pip install --upgrade Django==5.2.15
# 验证版本
python -m django --version
升级后建议做两项检查:
- 邮件发送路径:确认
EMAIL_USE_SSL/EMAIL_PORT配置是否符合预期,特别是从 STARTTLS 切换到 SSL 的项目,需要验证邮件仍能正常发出。 - 缓存行为:在 staging 环境中,用不同认证用户访问同一视图,确认响应不再共享缓存。可以在响应头中检查
Vary: Authorization是否出现。
风险评估与优先级
五个漏洞均为低严重度,但影响面不同:
| CVE | 核心风险 | 实际触发难度 |
|---|---|---|
| 6873 | Cookie 跨上下文接受 | 需要刻意构造 name/salt 组合,概率低 |
| 7666 | 邮件明文传输 | STARTTLS 握手失败 + 连接池复用,网络不稳定时可能触发 |
| 8404 | 缓存泄漏私有数据 | 需手动设置大小写混合的 Cache-Control |
| 35193 | 缓存泄漏私有数据 | 任何带 Authorization 头的请求,触发面最广 |
| 48587 | 缓存泄漏私有数据 | 需上游服务或代码在 Vary 头中引入空白字符 |
优先级建议:35193 最值得关注——只要你的站点有认证用户且使用了 UpdateCacheMiddleware,就可能在不同用户间泄漏页面内容。其余四个触发条件相对苛刻,但仍建议随常规升级一并修复,不要拖延到下一个版本周期。