SameSite 那些事

2021-06-03
8分钟阅读时长

提醒:本文最后更新于 1257 天前,文中所描述的信息可能已发生改变,请谨慎使用。

Featured Image

《Web 安全漏洞之 CSRF》中我们了解到,CSRF 的本质实际上是利用了 Cookie 会自动在请求中携带的特性,诱使用户在第三方站点发起请求的行为。除了文中说的一些解决方式之外,标准还专门为 Cookie 增加了 SameSite 属性,用来规避该问题。Chrome 于 2015 年 6 月支持了该属性,Firefox 和 Safari 紧随其后也增加了支持。SameSite 属性有以下几个值:

  • SameSite=None:无论是否跨站都会发送 Cookie
  • SameSite=Lax:允许部分第三方请求携带 Cookie
  • SameSite=Strict:仅允许同站请求携带 Cookie,即当前网页 URL 与请求目标 URL 完全一致

该属性适合所有在网页下的请求,包括但不限于网页中的 JS 脚本、图片、iframe、接口等页面内的请求。可以看到 None 是最宽松的,和之前的行为无异。而 LaxStrict 都针对跨站的情况下做了限制。其中 Strict 最为严格,不允许任何跨站情况下携带该 Cookie。Lax 则相对宽松一点,允许了一些显式跳转后的 GET 行为携带。以下是一个带有 SameSite 属性的标准 Cookie 响应示例:

Set-Cookie: name=lizheming; SameSite=None; Secure

需要注意的是,浏览器做了仅针对 HTTPS 域名才支持 SameSite=None 配置。所以如果你要设置 SameSite=None 的话,则必须还要携带 Secure 属性才行。

Same Site

Same Site 直译过来就是同站,它和我们之前说的同域 Same Origin 是不同的。两者的区别主要在于判断的标准是不一样的。一个 URL 主要有以下几个部分组成:

可以看到同域的判断比较严格,需要 protocol, hostname, port 三部分完全一致。相对而言,Cookie 中的同站判断就比较宽松,主要是根据 Mozilla 维护的公共后缀表(Pulic Suffix List)使用有效顶级域名(eTLD)+1的规则查找得到的一级域名是否相同来判断是否是同站请求。

例如 .org 是在 PSL 中记录的有效顶级域名,imnerd.org 则是一级域名。所以 https://blog.imnerd.orghttps://www.imnerd.org 是同站域名。而 .github.io 也是在 PSL 中记录的有效顶级域名,所以 https://lizheming.github.iohttps://blog.github.io 得到的一级域名是不一样的,他们两个是跨域请求。

在类似 GitHub/GitLab Pages, Netlify, Vercel 这种提供子域名给用户建站的第三方服务中,eTLD 的这种同站判断特性往往非常有用。通过将原本是一级域的域名添加到 eTLD 列表中,从而让浏览器认为配有用户名的完整域名才是一级域,有效解决了不同用户站点的 Cookie 共享的问题。

eTLD

eTLD 的全称是 effective Top-Level Domain,它与我们往常理解的 Top-Level Domain 顶级域名有所区别。eTLD 记录在之前提到的 PSL 文件中。而 TLD 也有一个记录的列表,那就是 Root Zone Database。RZD 中记录了所有的根域列表,其中不乏一些奇奇怪怪五花八门的后缀。

eTLD 的出现主要是为了解决 .com.cn, .com.hk, .co.jp 这种看起来像是一级域名的但其实需要作为顶级域名存在的场景。这里还可以分享一个有趣的事情,2020年5月份出现了一起阿里云所有 ac.cn 后缀网站解析全部挂掉的事件。原因就是 ac.cn 是中科院申请在册的 eTLD 域名。而阿里云的检测域名备案的脚本不了解规范,没有使用 PSL 列表去查找一级域名,而是使用了.分割的形式去查找的。最终所有 *.ac.cn 的域名由于 ac.cn 这个域名没有进行备案导致解析全部挂掉。而我们现在知道 ac.cn 这个域名是 eTLD 域名,它肯定是无法备案的。

Schemeful Same Site

在 Chrome 86/Firefox 79 中,浏览器增加了一个 Schemeful Same Site 的选项,将协议也增加到了 Same Site 的判断规则中。但是并不是完全的不等判断,可以理解是否有 SSL 的区别。例如 http://https:// 跨站,但 wss://https:// 则是同站,ws://http:/ 也算是同站。

Chrome 可以浏览器输入 chrome://flags/#schemeful-same-site 找到配置并开启。

Lax

我们知道互联网广告通过在固定域 Cookie 下标记用户 ID,记录用户的行为从何达到精准推荐的目的。随着全球隐私问题的整治,同时也是为了更好的规避 CSRF 问题,在 Chrome 80 中浏览器将默认的 SameSite 规则从 SameSite=None 修改为 SameSite=Lax。设置成 SameSite=Lax 之后页面内所有跨站情况下的资源请求都不会携带 Cookie。由于不会为跨站请求携带 Cookie,所以 CSRF 的跨站攻击也无从谈起,广告商也无法固定用户的 ID 来记录行为。

对用户来说这肯定是一件好事。但是对我们技术同学来说,这无疑是上游给我们设置的一个障碍。因为业务也确实会存在着多个域名的情况,并且需要在这些域名中进行 Cookie 传递。例如多站点使用 SSO 登录、接入统一的验证码服务、前端和服务端接口属于两个域名等等情况,都会因为这个修改受到影响。

这个修改影响面广泛,需要网站维护者花大量的时间去修改适配。而 Chrome 80 于 2020 年 2 月发布后全球就开始面临新冠疫情的影响。考虑到疫情问题后续的版本里又暂时先回退了这个特性(相关链接),最终是在 Chrome 86 进行了全量操作。

针对因为此次特性受到影响的网站,可以选择以下一些适配办法:

  1. 使用 JWT 等其它非 Cookie 的通信方式
  2. 为 Cookie 增加 SameSite=None;Secure 属性配置
  3. 所有的跨域接口增加 Nginx 代理,使其和页面保持同域

每一种方法都需要一些取舍。第一种更换 Cookie 的方式改造成本非常高,特别是在有外部业务对接的情况下基本不可能。第三种方式通过将跨域变为同域的转发方式可能会带来线上流量的成倍增加,也是需要考虑的因素。第二种设置成 None 看起来是比较简单的办法,不过也有着诸多的限制。

  1. SameSite=None;Secure 由于仅支持 HTTPS 页面,所以如果有 HTTP 的场景需要考虑跳转至 HTTPS 或者选择其他方案;
  2. 由于 SameSite 属性是后来才加入的,一些老浏览器(其实就是 IE)会忽略带有这些属性的 Cookie,所以需要同时下发未配置 SameSite 属性和配置 SameSite 属性的两条 Set-Cookie 响应头,这样支持和不支持的会各取所需;
  3. 在 Safari 的某些版本中会将 SamteSite=None 等同于 SameSite=Strict 所以部分 Safari 场景需要特殊处理不进行下发(相关链接);

综上使用代理转发的方式是我比较推荐的方式,除了不那么绿色之外兼容问题处理还是不错的。

SameParty

SameSite=Lax 断了我们跨站传递 Cookie 的念想,但实际业务上确实有这种场景。例如 Google 自己就有非常多的域名,这些域名如果都需要共享登录 Cookie 的话可能就会非常困难了。针对这种某个实体拥有多个域名需要共享 Cookie 的情况,就有人(那其实就是 Google 的同学)提出了 SameParty 的概念。

该提案提出了 SameParty 新的 Cookie 属性,当标记了这个属性的 Cookie 可以在同一个主域下进行共享。那如何定义不同的域名属于同一主域呢?主要是依赖了另外一个特性 first-party-set 第一方集合。它规定在每个域名下的该 URL /.well-known/first-party-set 可以返回一个第一方域名的配置文件。在这个文件中你可以定义当前域名从属于哪个第一方域名,该第一方域名下有哪些成员域名等配置。

当然使用固定 URL 会产生额外的请求,对页面的响应造成影响。也可以直接使用 Sec-First-Party-Set 响应头直接指定归属的第一方域名。

不过 W3C TAG 小组已经强烈拒绝了该提案(来源)。W3C 认为该提案重新定义了网站沙箱的边界,带来的影响可能不仅仅只是 Cookie 共享这么简单,包括麦克风、摄像头、地理信息等隐私设置都需要去重新评估影响。

同时该提案可能会和用户的预期不一致,如果 Google 和 Youtube 被定义成第一方网站进行共享的话,那 Google 就能很轻松的获取到用户在 Youtube 上的行为,可能用户并不想要这样。

W3C TAG 小组全称是 Technical Architecture Group,即 W3C 技术架构组。TAG 是 W3C 专注于 Web 架构管理的特殊小组。其使命是为 Web 架构的设计原则寻求共识,且在必要时梳理并澄清这些设计原则,帮助协调 W3C 内部及外部跨越不同技术的架构定义与研发工作。基本可以认为它是 Web 基础规范定义的小组。另外万维网之父 Tim Berners-Lee 也在 TAG 小组中。

不过 W3C 说的有理没理,都阻挡不了 Chrome 去实现这个功能。在 Chrome 89 中已经增加了 SameParty 的相关逻辑,只是目前没有默认开启。目前在 DevTools 中是可以看到 Cookie 的 SameParty 属性列的。Edge 由于使用了 Chromium 也在同版本支持了该功能。只掌管了规范,没有掌管实现,当某一方浏览器实现了“霸权”的情况下,W3C 的处境就变得尴尬了起来。

FLoC

SameSite 除了影响单实体多域名共享 Cookie 的情况,最大的问题其实就是互联网广告获取用户行为了。由于广告挂载页面和广告不在同域,所以广告无法获得用于标记用户 ID 从而对用户行为进行聚类。为了解决这个问题,有人(其实也是 Google 的同学)提出了 Federated Learning of Cohorts 同盟学习队列提案。

有别于之前使用 Cookie ID 标记直接将用户行为数据传递到广告商网站处理的方式。它提出了 document.interestCohort() 这个新的 API,将用户的行为在本地转换成了不带个人隐私的关键词,既规避了用户隐私问题,同时又解决了广告的精准投放问题。

不过这看似美好的东西却遭到了各大网站和浏览器的强力抵制,bravVivaldiduckduckgoGitHub 以及 Edge,Firefox,Safari(来源)都纷纷发表了拒绝支持的观点和行动。

社区主要的担心点在于,新的特性的增加可能会增加特征值为隐私嗅探提供了更广阔的入口。而且通过该 API 能获取到之前碍于权限无法程序获取的用户浏览数据。目前 Chrome 已经支持了这个功能,不过需要开启 Flag 才能支持。amIFLoCed 是一个用来检测你的浏览器是否开启了 FLoC 追踪特性的网站,可以使用它检测你的浏览器是否应用该特性。

后记

为了解决 CSRF 问题,Chrome 强推了 SameSite=Lax 作为默认配置。随之而来的,不仅是全球开发者的配合修改,还造成了已有场景的无法满足。而为了满足现有场景,又提出了 SamePartyFLoC 两个方案。这种行为不知能否成为浏览器的内卷行为?

SameSite 属性本身是没有什么问题的,但个人认为它应该是一种 CSRF 问题的选择方案,浏览器将其默认修改成 SameSite=Lax 就有点难受了。大部分企业项目里都已经采用其他 CSRF 防范方式规避了该问题,而 Lax 配置又存在着兼容性问题,不能让我们完全免顾 CSRF 之忧。

随着全球隐私问题的白热化,不知道还有什么新的提案搞出来需要我们全球开发者为其买单。

参考资料:

Avatar
怡红公子 擅长前端和 Node.js 服务端方向。热爱开源时常在 Github 上活跃,也是博客爱好者,喜欢将所学内容总结成文章分享给他人。

4 评论

石松岩 WeChat8.0 Android 10
2021-06-12 10:57:12 回复

imnerd.org 是二级域名,一级域名就是顶级域名

怡红公子 Chrome91.0 Mac OS 10.15.7
2021-06-12 12:22:57 回复

@石松岩 , 你这么说没有什么问题,只是我觉得大家把主域名叫一级域名叫习惯了,这么写实在是太别扭了,所以还是用一级域名和顶级域名区分了一下,文章整体也会顺畅一点~

石松岩 Chrome91.0 Mac OS 10.14.6
2021-06-12 14:34:35 回复

@怡红公子 , 确实很多人容易把二级域名叫做一级域名,有些是因为习惯,有些可能是不知道。我之前也因为这个东西疑惑过,后来查了查资料才明白了。

“SameSite=None 断了我们跨站传递 Cookie 的念想,但实际业务上确实有这种场景。” 这句中是不是写错了,应该是 SameSite= Strict 吧。

内容挺好的,赞👍

怡红公子 Chrome91.0 Mac OS 10.15.7
2021-06-12 14:40:54 回复

@石松岩 , 是的,原文是想说默认值的修改带来的影响,感谢指出,已经修改。非常感谢你对内容的喜欢,如果还有什么有待商榷的地方请随时指出~

另外关于顶级域名叫一级域名的,我觉得也不用特别拘泥于这块,之前我好像也查过规范,似乎没有特别定义这块的内容。所以我觉得大家只要明白在说什么就行,我自己一般就是用顶级域名是零级域名,之后就是一级域名这么来洗脑自己的[哭笑不得]。