抵御跨子域cookie劫持的核心是精确设置domain属性:禁用前导点号,静态指定完整域名(如'app.dev.fox.com')或主域('dev.fox.com'),或多入口时通过白名单动态绑定;再叠加httponly、secure、samesite=lax构成纵深防御。

要抵御跨子域的 Cookie 劫持与篡改,核心不是“加更多属性”,而是让 Cookie 只出现在它该出现的那个域名上——不多一分,不少一毫。Domain 属性一旦宽松,攻击面就指数级扩大;一旦精确,很多子域级越权和会话泄露问题自然消失。
严格禁用前导点号(.)是第一道防线
浏览器对 Domain 的解析遵循 RFC 6265:带前导点号(如 .dev.fox.com)表示“所有以 dev.fox.com 结尾的主机名”,即 app.dev.fox.com、api.dev.fox.com、甚至 evil.dev.fox.com 都能收到并发送该 Cookie。这不是 Django 的 bug,而是浏览器标准行为。
- ❌ 错误写法:
SESSION_COOKIE_DOMAIN = '.dev.fox.com'→ 所有子域共享会话,极易被劫持 - ✅ 正确写法:
SESSION_COOKIE_DOMAIN = 'app.dev.fox.com'→ 仅该完整域名可读写,其他子域完全隔离 - ✅ 同样安全:
SESSION_COOKIE_DOMAIN = 'dev.fox.com'→ 仅主域生效,不含任何子域(注意:这不等于通配)
按请求 Host 动态绑定域名,实现多入口零共享
当应用需支持多个独立子域(如 admin.dev.fox.com 和 user.dev.fox.com),且要求彼此会话完全隔离时,静态配置无法满足。此时必须动态生成 Domain 值,并配合白名单校验防止 Host 头注入。
- 在
settings.py中设为SESSION_COOKIE_DOMAIN = None,关闭默认写入 - 编写中间件,从
request.get_host()提取主机名(自动剥离端口) - 只允许预定义白名单内的域名作为 cookie_domain,例如:
['admin.dev.fox.com', 'user.dev.fox.com'] - 将匹配成功的域名直接赋值给
request.session.cookie_domain,Django 会在响应头中如实写出
配合 HttpOnly + Secure + SameSite=Lax 构成纵深防御
精准 Domain 是基础,但单靠它无法防 XSS 窃取或 MITM 篡改。必须叠加三项关键属性:
- HttpOnly:阻止 JavaScript 读取 Cookie(防 XSS 盗取 sessionId)
- Secure:强制仅 HTTPS 传输(防明文截获)
- SameSite=Lax:默认阻止跨站 POST 请求携带 Cookie(防 CSRF,同时保留导航类 GET 的可用性)
这些属性需在 Django 设置中统一开启:SESSION_COOKIE_HTTPONLY = True、SESSION_COOKIE_SECURE = True、SESSION_COOKIE_SAMESITE = 'Lax'。它们与 Domain 精确控制协同工作,缺一不可。
不推荐依赖 IP 绑定等客户端侧方案
有人尝试用 JavaScript 在前端将 Cookie 与用户 IP 绑定,或服务端校验请求 IP 是否匹配首次登录 IP。这类做法存在明显缺陷:
- 用户可能使用 NAT、代理或移动网络,IP 频繁变动导致正常用户反复掉线
- JavaScript 绑定完全不可信(可被绕过),服务端校验 IP 也易受 X-Forwarded-For 伪造影响
- 增加复杂度却未显著提升安全性,反而损害体验和可观测性
真正有效的隔离,来自服务端对 Domain 的刚性控制 + 标准安全属性的强制启用,而非在不可信环节叠加强制逻辑。











