【译】SameSite cookies 理解

3,082 阅读11分钟

原文:【Rowan Merewood】SameSite cookies explained

通过学习如何显式标记跨站(cross-site)cookies来保护您的站点。

Cookies 概述

Cookies 是可用于向网站添加持久状态的方法之一。多年来,它们的能力不断发展和壮大,但是给平台遗留了一些问题。为了解决这个问题,浏览器(包括 Chrome,Firefox 和 Edge)正在更改其行为,以强制实施更多保留隐私的默认设置。

每一个 cookie 都是一个 key=value 键值对,以及一些控制何时和何处使用该 cookie 的属性。你可能已经使用过这些属性来设置诸如到期时间之类的信息,或指示 cookie 仅应通过 HTTPS 发送。服务器通过在响应中发送 Set-Cookie 头来设置 cookie。有关所以详细信息,可以深入研究 RFC6265bis。这里只是快速入门。

假设您有一个博客,希望在其中向用户展示“What's new”促销。用户可以撤销该促销,然后就在一段时间内看不到了。您可以将此首选项存储在 cookie 中,将其设置为在一个月(2,600,000 秒)内到期,然后仅通过 HTTPS 发送。该标头看起来像这样:

Set-Cookie: promo_shown=1; Max-Age=2600000; Secure

服务器使用Set-Cookie标头设置 cookie

服务器使用`Set-Cookie`标头设置 cookie

当您的读者查看满足这些要求的页面时,即他们处于安全连接并且 cookie 的使用期限不到一个月,那么他们的浏览器就会在请求中发送此标头:

Cookie: promo_shown=1

浏览器将通过 Cookie 标头将 cookies 发送回去

您也可以使用 document.cookie 在 JS 中添加和读取可用于该站点的 cookies。对 document.cookie 进行分配将使用该秘钥创建或覆盖 cookie。例如,您可以在浏览器的 JS 控制台中尝试以下操作:

> document.cookie = "promo_shown=1; Max-Age=2600000; Secure"
< "promo_shown=1; Max-Age=2600000; Secure"

读取 document.cookie 将输出在当前上下文中可访问的所有 cookie,每个 cookie 用分号分隔:

> document.cookie;
< "promo_shown=1; color_theme=peachpuff; sidebar_loc=left"

JS 可以通过 document.cookie 访问 cookie

如果您在一些受欢迎的站点上尝试此操作,您就会发现大多数站点设置的数量远远超过了三个 cookies。在大多数情况下,这些 cookie 是在每个单个请求上发送到该域的,这有很多含义。对于您的用户,上传带宽通常比下载带宽受到更多限制,因此所有出站请求的开销都会增加您第一个字节到达的时间延迟。保守设置您 cookies 的数量和大小。利用 Max-Age 属性可帮助确保 cookie 的停留时间不会超过所需的时间。

什么是 first-party 和 third-party cookies?

如果您回到上一节选择的站点,您可能会注意到针对各种域的 cookie,而不仅仅是您当前正在访问的 cookie。与当前网站的域(即浏览器地址栏中所显示的)所匹配的 cookie 称为 first-party cookies。来自当前站点其他的域的 cookie 称为 third-party cookies。这不是绝对的标签,而是相对于用户的上下文。同一 cookie 既可以是 first-party 又可以是 third-party,这取决于用户当时所在的网站。

Cookie 可能来自一页上的各种不同域

继续上面的示例,假设您的一篇博客文章中包含一张特别令人惊叹的猫的图片,并将其托管于 /blog/img/amazing-cat.png。因为这是一个不错的图片,所以有另一个人直接在他们的网站上使用它。如果访问者访问过您的博客并具有 promo_shown cookie,那么当他们在其他人的站点上查看 amazing-cat.png 时,将在该图片请求中发送cookie。这对任何人都不太有用,因为promo_shown并未用于此人网站上的任何内容,它只是增加了请求的开销。

如果这是意想不到的效果,那为什么要这样做呢?因为通过这种机制,站点可以在 third-party 上下文中使用时保持状态。比如,如果您在自己的网站上嵌入 YouTube 视频,则访问者将在播放器中看到“稍后观看”选项。如果您的访问者已经登录 YouTube,则该会话将会通过 third-party cookie 在嵌入式播放器中提供——意味着“稍后观看”按钮将一次性保存视频,而不是提示他们登录或必须从您的页面上离开并返回 YouTube。

在访问不同页面时,将发送 third-party 上下文中的 cookie

Web 的文化属性之一是默认情况下它是开放的。这是让如此多的人在 Web 上创建自己的内容和应用程序的原因之一。然而,这也带来了许多安全和隐私问题。跨站点请求伪造(CSRF)攻击依赖于将 cookie 附加到给定源的请求,而无论谁发起请求。比如,如果您访问evil.example,则它可以发出对your-blog.example的请求,并且您的浏览器会附加关联的 cookie。如果您的博客对如何验证这些请求不关心,那么 evil.example 可能会触发删除帖子或添加自己的内容之类的操作。

用户也越来越意识到 cookie 是如何跟踪他们跨多个站点的活动的。但是,到目前为止,还没有一种方法可以明确地表示您对 Cookie 的意图。您的 promo_shown cookie 仅应在 first-party 上下文中发送,而故意嵌入到其他站点的窗口会话 cookie 则有意在 third-party 上下文中提供登录状态。

使用 SameSite 属性明确声明 Cookie 的使用情况

SameSite 属性(在 RFC6265bis 中定义)使您可以声明 cookie 是否应限于 first-party 或 same-site 上下文。准确了解这里的 “site”(站点) 的含义很有帮助。该站点是域后缀和后缀之前的域的组合。例如:www.web.dev 域是 web-dev 站点的一部分。


关键术语: Same-Site

如果用户在 `www.web.dev`上并从 `static.web.dev` 请求图片,则这是一个 same-site 的请求

公共后缀public suffix list)对此进行了定义,因此它不仅是 .com 之类的顶级域名,还包括 github.io 之类的服务。your-project.github.icmy-project.github.io 可以算作单独的站点。


关键术语:Cross-Site

如果用户在 `your-project.github.io` 上,并向 `my-project.github.io` 请求图片,则这是 一个 cross-site 请求

在 cookie 上引入 SameSite 属性可提供三种不同的方式来控制此行为。您可以选择不指定属性,也可以使用 StrictLax 将 cookie 限制为 same-site 请求。

如果将 SameSite 设置为 Strict,则 cookie 仅会在 first-party 上下文中发送。用用户术语来讲,只有在 Cookie 的站点与浏览器的 URL 栏中当前显示的站点匹配时,才会发送 cookie。因此,如果将 promo_shown cookie 设置如下:

Set-Cookie: promo_shown=1; SameSite=Strict

当用户在您的网站上时,该 cookie 将与预期的请求一起发送。但是,当您连接到您网站的链接时,例如从另一个网站或通过朋友发送的 email,在该初始请求中,将不会发送 cookie。当您有与功能相关的 cookie 总是处于初始导航之后(例如更改密码或购买商品),但对 promo_shown 来说太严格了。如果您的读者访问该站点的链接,则它们希望发送 cookie,以便可以应用其首选项。

这就是 SameSite=Lax 的应用场景,它允许通过这些顶级导航发送 cookie。让我们从上面重新访问猫的文章的例子,其中另一个站点引用了您的内容。他们直接利用猫的图片,并提供指向您原始文章的链接。

<p>Look at this amazing cat!</p>
<img src="https://blog.example/blog/img/amazing-cat.png" />
<p>Read the <a href="https://blog.example/blog/cat.html">article</a>.</p>

cookie 已经被设置如下:

Set-Cookie: promo_shown=1; SameSite=Lax

当读者在其他人的博客时,如果浏览器请求 amaing-cat.png,则不会发送该 cookie。但是当读者通过链接访问您博客上的 cat.html 时,该请求将包含 cookie。这使得 Lax 成为影响网站显示的 cookie 的不错选择,而 Strict 对于与您的用户执行的操作相关的 Cookie 很有用。


注意

不论 `Strict` 还是 `Lax` 都不是网站安全的完整解决方案。Cookies 是作为用户请求的一部分发送的,您应该将它们与其他任何用户输入一样对待。这意味着对输入进行消毒和验证。切勿使用 cookie 来存储您认为是服务器端机密的数据。

最后,可以选择不指定值,该值以前是隐式声明您希望在所有上下文中发送 cookie 的方式。在 RFC6265bis 的最新草案中,通过引入 SameSite=None 的新值来明确指出这一点。这意味着您可以使用 None 来明确表示您有意要在第三方上下文中发送 cookie。

将 cookie 的上下文明确标记为 None, Lax 或者 Strict
如果您使用其他站点使用的服务,例如小部件、嵌入式内容、会员计划、广告或跨多个站点登录,则应使用 `None` 来确保您的意图明确

在没有 SameSite 的情况下更改默认行为

尽管 SameSite 属性得到了广泛支持,但不幸的是,它尚未被开发人员广泛采用。开放的默认发送 Cookie 意味着所有使用用例都可以使用,但使用户容易受到 CSRF 和意外信息泄露的影响。为了鼓励开发人员陈述其意图并未用户提供更安全的体验,IETF 提案《渐进式更好的 Cookies》提供了两个关键更改:

  • 没有 SameSite 属性的 Cookies 会被认为是 SameSite=Lax
  • SameSite=None 的 Cookie 也必须指定 Secure,这意味着它们需要安全的上下文

Chrome 自 80 版本其就实现了这些行为。Firefox 从 Firefox 69 其就可以对其进行测试,并将在将来使它们成为默认行为。要在 Firefox 中测试这些行为,请打开 abount:config 并设置 network.cookie.sameSite.laxByDefaultEdge也计划更改其默认行为。


本文将在其他浏览器宣布支持时进行更新(本译文不一定会跟进哦)

默认 SameSite=Lax

未设置属性:

Set-Cookie: promo_shown=1

如果发送 cookie 时没有设置任何 SameSite 属性

默认行为:

Set-Cookie: promo_shown=1; SameSite=Lax

浏览器会将该 cookie 视为已设置 SameSite=Lax

虽然这是为了应用更安全的默认值,但理想情况下,您应该设置一个明确的 SameSite 属性,而不要依赖浏览器为您应用该属性。这使您对 Cookie 的意图明确,并提高了跨浏览器获得一致体验的机会。


注意

与默认的 SameSite=Lax 相比,Chrome 所应用的默认行为要宽松一些,因为它将允许某些 Cookie 在顶级 POST 请求上发送。您可以在 blink-dev 公告 中看到确切的详细信息。这只是暂时的环节错误,您仍应修复跨站点 Cookies 以使用 SameSite=None; Secure

SameSite=None 必须是安全的

不加 Secure 设置 cookie 会被拒绝:

Set-Cookie: widget_session=abc123; SameSite=None

必须确保将 SameSite=NoneSecure 属性配对:

Set-Cookie: widget_session=abc123; SameSite=None; Secure

要测试此行为,可以在 Chrome 76 中启用 chrome://flags/#cookies-without-same-site-must-be-secure ,以及在 Firefox 69 中 设置 about:config: network.cookie.sameSite.noneRequiresSecure 您将需要在设置新的 Cookie 时应用此功能,并主动刷新现有的 Cookie,即使这些 Cookie 的有效期限未到。


如果您依赖于在您的站点上提供第三方内容的任何服务,则还应该向提供商咨询他们正在更新其服务。您可能需要更新依赖项或代码片段,以确保您的网站采用了新的行为。

这两个更改都已正确实现 SameSite 属性的先前版本或根本不支持它的浏览器向后兼容。通过将这些更改应用于 cookies,您可以明确指定其预期用途,而不是依赖浏览器的默认行为。同样,任何尚未识别 SameSite=None 的客户端都应忽略它,并像未设置属性一样继续操作。


警告

很多旧版本的浏览器(包括 Chrome,Safari 和 UC 浏览器)与新的 None 属性不兼容,并且可能会忽略或限制 Cookie。此行为在当前版本中已修复,但是您应该检查流量以确定受影响的用户比例。您可以在 Chromium 网站上查看已知的不兼容客户端列表

SameSite cookie recipes

有关准确更新 cookie 的详细信息,以成功处理对 SameSite=None 的这些更改以及浏览器行为的差异,请转到后序文章:SameSite cookir recipes