跳到内容

Cookie 安全标志完全指南|Secure / HttpOnly / SameSite / __Host-

分类:Web 安全

Cookie 是 Web 应用程序会话管理中最常使用的机制,但如果配置不当,可能导致 会话劫持 (session hijacking)、CSRF (cross-site request forgery) 和通过 XSS 进行的令牌盗取等攻击。本文阐述了应应用于 Cookie 的四个关键标志 (Secure / HttpOnly / SameSite / __Host- 前缀) 的含义,并梳理了实现中的常见陷阱。

Set-Cookie 头结构

当服务器在浏览器中设置 Cookie 时,它在 HTTP 响应中返回如下的标头。

Set-Cookie: session_id=abc123; Path=/; Domain=example.com; Expires=Wed, 22 Apr 2026 10:00:00 GMT; Secure; HttpOnly; SameSite=Lax

有多个用分号分隔的属性,大致可分为以下两种。

  1. 作用域属性:Path / Domain / Expires / Max-Age — 决定 Cookie 何时何地发送
  2. 安全属性:Secure / HttpOnly / SameSite — 限制传输/访问

Secure — 仅通过 HTTPS 发送

具有 Secure 属性的 Cookie 仅在 HTTPS 连接时 由浏览器发送到服务器。如果没有它,当受害者通过 http:// 访问网站时,Cookie 以明文形式传输,会话 ID 可能会通过中间人 (Man-in-the-Middle) 攻击被窃取。

金科玉律:必须附加到身份验证 cookie。在本地开发中,localhost 即使没有 HTTPS 也会发送 Secure cookie,但只要假设生产环境使用 HTTPS,就不会有问题。

HttpOnly — 无法从 JavaScript 访问

添加 HttpOnly 标志可防止通过 document.cookie 读取。这可以防止 XSS 攻击注入的 JavaScript 窃取 Cookie。

由于 XSS 仍然是可能的漏洞,请将 HttpOnly 标志视为会话 Cookie 的必须项。如果您的 JavaScript 代码需要读取 Cookie 值(例如 CSRF 令牌),标准做法是创建单独的只读 Cookie 或通过 <meta> 标签 传递值。

SameSite — 跨站请求中的传输控制

SameSite 属性是 CSRF 防护的核心。值有以下三种:

行为CSRF 防护
Strict根本不向跨站请求发送(即使来自外部链接)最强大
Lax (默认)仅在顶级 GET 导航中发送(链接点击 OK,POST 表单 NG)
None随所有跨站请求发送(需要Secure)

2020年以后的现代浏览器将未指定的SameSite视为Lax,因此即使不显式指定也能获得最低限度的CSRF保护。但为了明确意图,最好显式设置。

当在 SSO 集成或 iframe 嵌入中使用 SameSite=None 时,必须同时添加 Secure—这是现代浏览器的要求。没有 Secure 的 SameSite=None 的 Cookie 会被浏览器拒绝。

__Host- / __Secure- 前缀

当 Cookie 名称以 __Host- 开头时,浏览器会强制执行以下三个条件。

  • Secure 标志是必需的
  • 不应该添加 Domain 属性(即仅限发送请求的准确主机)
  • Path=/ 是必需的

这些提供强有力的保证:「不能从其他子域名被覆盖」「不能通过Host头欺骗来设置Cookie」。对于会话Cookie,添加像__Host-session这样的前缀最为稳健。

另一个 __Secure- 前缀仅强制必需的Secure标志 (没有Domain / Path限制)。

4096字节限制

根据 RFC 6265,Cookie 的值建议为 总共 4096 字节。如果将大的 JSON 或数组放入 Cookie 中并超过此限制,浏览器可能会悄悄截断它们。最佳做法是在服务器端会话中存储大数据,只在 Cookie 中放入会话 ID。

实现示例:身份验证会话 Cookie

正确配置的身份验证 Cookie 如下所示:

Set-Cookie: __Host-session=eyJ0eXAi...; Path=/; Max-Age=3600; Secure; HttpOnly; SameSite=Lax
  • __Host-: 防止子域名污染和主机欺骗
  • Path=/: 在整个网站上可访问
  • Max-Age=3600: 1 小时后过期
  • Secure: 仅通过 HTTPS 发送
  • HttpOnly: 无法从 JavaScript 访问
  • SameSite=Lax: 不在跨站点 POST 请求中发送 (CSRF 保护)

PHP (Laravel) 示例

// config/session.php
return [
    'secure'     => true,      // Secure フラグ
    'http_only'  => true,      // HttpOnly フラグ
    'same_site'  => 'lax',    // SameSite=Lax
    'path'       => '/',
    'cookie'     => '__Host-session',
];

Node.js (Express) 示例

const session = require('express-session');

app.use(session({
  name: '__Host-session',
  secret: process.env.SESSION_SECRET,
  cookie: {
    secure:   true,
    httpOnly: true,
    sameSite: 'lax',
    path:     '/',
    maxAge:   3600 * 1000,
  },
}));

检查现有网站 Cookie 设置的方法

使用 DevLab 的 Cookie 检查工具,您可以立即验证您的网站或第三方网站上的 Cookie 是否设置正确。只需输入 URL,该工具就会分析所有返回的 Set-Cookie 标头,并显示如下诊断。

  • 每个 Cookie 的 Secure / HttpOnly / SameSite 有无
  • 像 SameSite=None 但没有 Secure 这样的违规
  • __Host- 前缀的一致性
  • 4096字节超出警告
  • 总体摘要(Secure比率 / HttpOnly比率 / SameSite分布)

总结

Cookie 安全标志不应该随意设置,而应该基于理解攻撃场景和相应的防御措施来配置。至少,生产环境中的身份验证会话 Cookie 应该包含 Secure + HttpOnly + SameSite + __Host- 前缀 + 短 Max-Age。对于现有网站的设置审查,我们推荐使用 Cookie 检查工具

❓ 常见问题

SameSite=Lax 与 Strict 该如何选择?
保持登录状态的会话 Cookie,默认答案是 Lax。设为 Strict 后,从外部站点链接返回的第一个请求不会携带 Cookie,已登录用户会被显示为未登录。只有确认支付、修改密码这类绝不能由其他站点触发的操作,才把对应 Cookie 设为 Strict。
加上 HttpOnly 就能防住 XSS 吗?
不能。HttpOnly 只阻止 XSS 得手之后通过 document.cookie 读取令牌,并不阻止脚本注入本身,攻击者完全可以就地发起请求而不必窃取。真正的防线是输出转义与 CSP,HttpOnly 只是把损失收窄一级的保险。
__Host- 前缀究竟保证了什么?
浏览器会强制要求 Secure、Path=/ 且不带 Domain 属性,不满足条件的 Set-Cookie 会被整条拒绝。收益在于子域名无法再覆写该 Cookie:因为不能设置 Domain,user.example.com 无法替换属于 example.com 的 Cookie,从结构上阻断了子域名被攻陷后的会话固定。