Featured image of post Cookies - SameSite Attribute

Cookies - SameSite Attribute

Cookies - SameSite Attribute

Photo by Mohammad Rahmani on Unsplash

Chrome 从 84 版开始将 Cookie 的 SameSite 属性预设为 Lax,使用到 Third-party cookies 的服务若没有设定 SameSite 都可能受到影响。

概览

Cookies 是网页服务中用来储存状态的机制,常被用在保持登入、购物车、广告追踪等等,但在 Cookies 的广泛使用下,同时也伴随着隐私和安全的疑虑,而 SameSite 的出现就是为了解决这些问题。

 

First-Party and Third-Party

依据 Cookie 的来源(Set-Cookie),每个 Cookie 都有专属的 Domain,以使用者浏览器当下的网址来看,只要 Cookie 的 Domain 和目前的网址相符就是 First-Party,反之就是 Third-Party。

Third-party

例如浏览 a.com 网站时发送 Request 到 third-party.com 并拿到了 third-party.com 的 Cookie,由于浏览器会在 Request 时自动带上相同 Domain 的 Cookie,之后浏览了其他网站如 b.com 时若也发送 Request 到 third-party.com,Server 就会收到 Cookie,对这两个网站来说 third-party.com 的 Cookie 就是 Third-party

First-party

如果浏览符合 third-party.com Domain 的网站也会带上 Cookie,此时这个 Cookie 就称为 First-party

 

Same-Origin and Same-Site

刚才的例子提到以 Domain 是否符合来判定 Cookie 的种类,不过更好的说法应该是以 Site 是否相同来判定,而这和常常看到的 Same-origin 是否有关呢?

Cookies SameSite

Origin

Origin 是由 Scheme, Host, Port 组成,判定方式非常简单,只要两个网址的 Scheme、Host 和 Port 都相同就是 Same-origin,其馀皆是 Cross-origin。

Site

Same-Site 的判定则牵涉到 Effetive top-level domains(eTLDs),所有的 eTLDs 被定义在 Public Suffix List 中,而 Site 是由 eTLD 加上一个前缀组成。

举例来说: github.io 存在 Public Suffix List 之中,加上一个前缀(例如 a.github.io) 就是一个 Site,因此 a.github.iob.github.io 是两个不同的 Site(Cross-site)。

example.com 不存在 Public Suffix List 之中,但 .com 存在,因此 example.com 是一个 Site,a.example.comb.example.com 就是同一个 Site(Same-site)。

注意 Site 不包含 Port,即使 Port 不同也可以是 Same-site

 

Why SameSite?

「任何 Request 都带上该 Domain 的 Cookie」的机制同时也带来了安全和其他问题,其中最重要的就是 Cross-site request forgery(CSRF)。

CSRF

假设使用者曾经登入过 example.com 并取得 Cookie,当使用者浏览恶意网站 evil.com 时,网站中的 JavaScript 可以对 example.com/pay?amount=1000 发出 POST Request,浏览器会自动带上 example.com 的 Cookie,使用者就在完全不知情的状况下付了 1000 元,Server 无法判定这个 Request 是从何而来。

限制

Cookie 本身无法被设定为只在 First-party 环境才发送,因此 Request 在任何环境都会带上 Cookie,Server 无法辨识 Request 来源只能照常回复,同时也让 Client 浪费流量送出无用的 Cookie,

解决

有了 SameSite 属性后,就可以个别设定 Cookie 在不同环境下的发送条件。

SameSite

SameSite 属性共有三种值,设定为 StrictLax 可以限制 Cookie 只在 Same-Site Request 带上,若不填则依据浏览器可能有不同行为,以 Chrome 来说预设值为 Lax

Strict

只在 First-party 环境下带上 Cookie,但这有个问题,假设使用者在 example.com 看到一条 FB 贴文连结(假设为 fb.com),就算使用者曾经登入过 fb.com 取得了 Cookie,点击连结后因为两个网站为 Cross-site,不会带上 Cookie,只能看到登入页面。

因此 Strict 适合用在操作,例如删除贴文、付款等等。

Lax

为了解决 Strict 过于严格的限制,Lax 在以下情况即使是 Cross-site 依然会送出 Cookie

  • 在网址列输入输入网址
  • 点击连结 <a href="...">
  • 送出表单 <form method="GET">
  • 背景转译 <link rel="prerender" href="...">

这几个情况有两个共通点:都是 GET 且皆会触发网页跳转(Navigation),如此一来就能避免 Strict 需要重新登入的问题,也不会在浏览其他网站时毫不知情的送出 Cookie

Lax + POST

然而为了避免破坏某些现有的登入流程,Chrome 目前在 SameSite=Lax 放宽了一点限制,给开发者更多时间喘息。

在 Cookie 被设定的两分钟内,无论 Request Method 是甚麽,只要触发 Top-level 页面跳转都会带上 Cookie,也就是让浏览器换了页面,例如送出表单 <form method="POST">

详情请见关于 Lax + POST 的[讨论串](https://groups.google.com/a/chromium.org/g/blink-dev/c/AknSSyQTGYs/m/YKBxPCScCwAJ

None

想要送出 Third-party cookie 就必须设定为 SameSite=None; Secure,没错,现在起想要在测试环境送出 Third-party cookie 请准备 https://localhost

另外以 XHR/Fetch 送出 Cross-Origin Request 需要另外设定 withCredentials: true 才会带上 Cookie 和让 Response header 的 Set-Cookie 生效,而 Server 端要在 Response header 中设定 Access-Control-Allow-Credentials: true,JavaScript 才能存取 Response 的内容。

不支援的浏览器

并不是所有浏览器都已经支援最新的 SameSite 规则,因此可以在 Server 加入一些暂时的 Workaround 来支援多种浏览器:

此种方式几乎可以解决所有浏览器的问题,缺点就是 Cookie 都会变成两份:

Set-cookie: name=value; SameSite=None; Secure
Set-cookie: name-legacy=value; Secure

Server 端的程式码:

if (req.cookies['name']) {
  // 有新的就用新的
  cookieVal = req.cookies['name'];
} else if (req.cookies['name-legacy']) {
  // 不然就用旧的
  cookieVal = req.cookies['name-legacy'];
}

User Agent

以 Request 的 User agent 判断浏览器来决定 Set-Cookie 的内容,这种方式只需要修改设定 Cookie 的程式码,不用修改 Parse 的部分,但这种判断方式相对变数较多,比较容易设定成错误的 Cookie 。

 

回顾

  • 没设 SameSite 属性的 Cookie 都会变成 SameSite=LaxCross-site 环境下无法送出
  • 想要 Cross-site 送出 Cookie 需要设定 SameSite=None; Secure
  • 可以用 SameSite sandbox 测试目前用的浏览器是否符合最新的 SameSite 规则。

Reference

All rights reserved,未經允許不得隨意轉載
Built with Hugo
主题 StackJimmy 设计