深入解析SameSite Cookie:跨站与跨源的安全迷思
TL;DR
- SameSite Cookie属性未被充分理解。
- 混淆站点(site)和源(origin)是常见但有害的错误。
- 站点的概念比表面看起来更复杂。
- 某些请求是跨源但同站的。
- SameSite仅对跨站请求生效。
- SameSite使你的子域名成为攻击目标。
- 误解可能导致开发者不适当地避免使用SameSite=Strict。
SameSite的出现
你一定听说过SameSite Cookie属性。2020年2月,Chrome开始改变SameSite的默认行为,使其成为头条新闻。作为防御跨站攻击(如CSRF和XSSI)的深度防御机制,SameSite自2016年诞生以来一直潜伏在浏览器实现中。2020年初SameSite的激活需要一些网站进行艰苦的调整以维持第三方访问,但被广泛誉为浏览器防御的受欢迎补充。
在SameSite激活前后,网络上的博客开始涌现文章,传播这一“新”Cookie属性的机制。
术语的随意使用
其中一些文章具有令人钦佩的精确性,如Rowan Merewood的web.dev文章《SameSite cookies explained》。不幸的是,相对较少的文章努力澄清站点的概念,而相同站点请求和跨站点请求的技术概念正是由此衍生的。此外,许多文章,包括由信息安全社区有影响力的成员制作的文章,似乎交替使用“源”和“站点”这两个术语,或至少使用得有些松散。
早在2019年2月,Kristian Bremberg在受人尊敬的Detectify博客上写道:
SameSite属性相当新,提供了出色的CSRF攻击保护。如果Cookie使用SameSite属性,Web浏览器将确保使用Cookie发出的请求来自设置Cookie的源。(我的强调)
信息安全超级明星Troy Hunt本人在2020年1月初发表的一篇开创性文章《Promiscuous Cookies and Their Impending Death via the SameSite Policy》中,描述了不同SameSite属性值的效果如下:
None:今天Chrome在没有设置SameSite值时的默认值 Lax:对跨源请求发送Cookie有一些限制 Strict:对跨源请求发送Cookie有严格限制(我的强调)
几个月后,在一篇分析SameSite的出现如何影响黑客珍视的一系列漏洞的引人入胜的文章中,Reconless团队写道:
更新后,所有没有显式SameSite属性的Cookie将被视为具有SameSite=Lax。这意味着跨源请求不再携带Cookie,除了顶级导航。(我的强调)
域名、主机、源、站点……
在非正式交流中松散使用这些术语是很自然的;这种术语使用的松散性可以原谅,如果你自己也有罪,那你并不孤单。
“站点”和“源”可以互换吗?
然而,SameSite Cookie属性的出现引发了一些问题……
在这里,仔细区分“源”和“站点”是否有必要?这是否只是没有区别的区别?跨站点请求与跨源请求没有不同吗?那么Cookie属性是否也可以命名为“SameOrigin”?或者,如果“站点”和“源”之间确实存在真实差异,这对实践者重要吗?如果差异重要,如何重要?
你可能已经从本文的标题中猜到了答案:在SameSite的上下文中,“站点”具有非常技术性的含义,但它被不适当地忽视了;站点和源之间的区别确实重要,但这两个概念经常被混淆。
这种术语上的失误并没有逃过所有人的注意。谷歌的Web开发者倡导者Eiji Kitamura在Chrome激活SameSite仅几个月后,就觉得需要 dedicate 一整篇博客文章来区分“源”和“站点”,这很能说明问题。
为了理解为什么这种区别重要,你首先需要理解源和站点之间的区别。
我们所说的“源”是什么意思?
如果你使用Web技术,你至少对同源策略(SOP)有些熟悉,它可以说是Web安全的主要支柱之一。URI的源的概念当然是SOP的核心,因此,它相对较好理解。RFC 6454的第3.2节将源定义为一个三元组:
粗略地说,两个URI是同一源的一部分(即代表同一主体),如果它们具有相同的方案、主机和端口。
端口是可选的;如果未指定,则暗示与方案关联的默认端口(例如,http为80,https为443)。MDN Web Docs有一系列澄清示例。
我们所说的“站点”是什么意思?
在“站点”这个极其通用的术语背后,隐藏着一个比源更难掌握的概念。一方面,“站点”并不总是一个技术术语:它早于SOP,并且在跨站脚本等攻击出现时已被普遍使用。此外,现代站点概念充满技术困难。它与主机的可注册域密切相关,URL Living Standard将其定义为
[…] 由最具体的公共后缀以及紧接其前的域标签(如果有)组成的域。(主机的可注册域也称为其“eTLD+1”,即“有效顶级域加一”的缩写。)
在最简单的情况下,源的站点仅对应于源主机的可注册域(如果有)。两个例子,以固定概念:
- https://www.example.org的站点是example.org,因为org是主机最具体的公共后缀,因此example.org是主机的eTLD+1。
- https://jub0bs.github.io的站点是jub0bs.github.io,因为github.io是主机最具体的公共后缀,因此jub0bs.github.io是主机的eTLD+1。
是的!也许令人惊讶的是,github.io是一个公共后缀!然而,值得注意的是,可注册域的概念是流动的,因为它依赖于公共后缀列表,这是一个不是一成不变而是会随时间变化的列表。更不用说不同的浏览器可能不一定以相同的速度跟上公共后缀列表的变化。
技术细节还不止于此!正如web.dev警告我们的那样,站点的概念仍在发展,并且很快将包含方案。这一变化目前隐藏在Chrome的一个标志后面,但很快就会推出。然而,为了回避这一困难并延长本文的相关性,我在下文中只考虑方案为https的源。
同站与跨站请求
既然我们已经处理了站点的概念,我们终于可以讨论同站和跨站请求的概念。一个给定的请求要么是同站的,要么是跨站的。请求是同站还是跨站取决于请求的源起源和目标起源的站点比较:
- 如果两个站点相同,则请求称为同站;
- 如果两个站点不同,则请求称为跨站。
以下是三个例子:
- 从https://foo.example.org发送到https://bar.example.org的请求是同站的,因为两个源的站点都是example.org。
- 从https://foo.github.io发送到https://bar.github.io的请求是跨站的,因为第一个源的站点是foo.github.io,而第二个源的站点是bar.github.io。
- 从https://foo.bar.example.org发送到https://bar.example.org的请求是同站的,因为两个源的站点都是example.org。
如果你已经读到本文的这里,我感谢你的耐心。振作起来:回报就在眼前!
跨源、同站请求
所有跨站请求 necessarily 是跨源的;这一点很清楚。然而,正如上面第一个和第三个例子所示,以及下面粗糙的维恩图所说明的,并非所有跨源请求都是跨站的。
SameSite Cookie属性仅关心跨站请求;它对恰好是同站的跨源请求没有影响。这就是源和站点之间的区别重要的原因。
在线演示
为了证明我的观点,我从Troy Hunt的文章中汲取灵感,并部署了一个简单的Go服务器到samesitedemo.jub0bs.com,由两个端点组成。端点/setcookie设置一个SameSite=Strict Cookie,如下所示:
|
|
端点/readcookie打印请求附带的Cookie(如果有)。我还设置了两个“攻击”页面:
两个页面都只包含一个指向https://samesitedemo.jub0bs.com/readcookie的链接。为了固定概念,以下是你可以做的:
- 导航到https://samesitedemo.jub0bs.com/setcookie。这样做将在你的浏览器中设置Strict Cookie。
- 导航到https://jub0bs.github.io/samesitedemo-attacker-foiled并跟随该页面上的链接。由于攻击URI的站点(jub0bs.github.io)与目标URI的站点(jub0bs.com)不同,浏览器不会将Cookie附加到跟随链接产生的请求中,并且响应中不会打印Cookie。SameSite=Strict按预期工作,“攻击”被挫败。
- 现在导航到https://samesitedemo-attacker.jub0bs.com/并跟随该页面上的链接。由于攻击URI的站点(jub0bs.com)与目标URI的站点(jub0bs.com)相同,浏览器确实将Cookie附加到跟随链接产生的请求中,并且Cookie确实在响应中打印。在这种情况下,SameSite Cookie属性根本不适用,“攻击”成功。
混淆站点和源的代价
错误的安全感
暗示SameSite适用于所有跨源请求是有害的,因为它可能导致实践者错误地认为SameSite保护他们的用户免受所有跨源滥用。这种误解对于忽视审查其子域名安全级别的实践者尤其危险。特别是,
- 子域名接管,或
- 同一站点的子域名上的跨站脚本(XSS)实例,或
- 同一站点的子域名上的HTML注入实例
可能足以让攻击者绕过SameSite提供的相对保护。
子域名接管是一种早在2014年由Detectify推广的攻击。它利用子域名上的悬空DNS记录来控制该子域名服务的部分或全部内容。攻击者可能利用子域名接管达到各种目的:篡改、网络钓鱼等……以及否则不可能的跨源攻击!例如,如果攻击者能够接管https://vulnerable.example.org,他或她可能能够从在易受攻击子域名上下文中运行的客户端代码发送恶意请求到https://example.org(或其任何子域名);并且这些请求是同站的,将携带所有相关Cookie,无论它们的SameSite属性值如何!
对于这种跨源、同站攻击,甚至可能不需要子域名接管。同一站点的子域名上存在XSS或HTML注入漏洞可能就足以让攻击者发送恶意跨源、同站请求。
注意:在上述两种情况下,“跨站请求伪造”是一个误称,因为攻击站点和目标站点是相同的;“同站跨源请求伪造”(SSCORF?)将是描述这种攻击的更合适的术语,但我怀疑它是否会达到普遍使用。
我发现令人着迷的是,SameSite可能会使精明的攻击者比过去更关注你的子域名和兄弟域名,因为这些域名正迅速成为针对经过战斗考验的Web应用程序的跨源攻击的唯一避难所。
SameSite=Strict的采用速度较慢
此外,这种误解也可能减缓Strict值的采用,而支持Lax值。有些人确实积极劝阻实践者使用Strict,因为他们认为它对可用性的阻碍比实际更大。例如,在Dareboost的博客上,你可以读到以下声明:
如果我们在dareboost.com上使用Strict Same-Site,通过点击此链接,无论你是否连接,都不会被检测为已登录。
该声明不正确:所讨论的链接存在于https://blog.dareboost.com上,并指向https://www.dareboost.com;因此,点击链接触发的请求将是同站的,并携带所有范围在www子域名或父域名的Cookie。作者总结道:
这种行为可能会让最终用户感到困惑,因此你宁愿使用Lax模式。
这只是不合理贬低Strict值的一个例子,但我相信你可以在网络上的其他地方找到更多例子。
临别赠言
我已经联系了所有我认为在描述SameSite机制时不准确的人。到目前为止,只有Detectify的Kristian Bremberg和Reconless的Edwin Foudil(也称为@edoverflow)回复了我。我高度希望他们在阅读本文后会修改他们的文章。我在Troy Hunt的文章上留下了公开评论,但他还没有回复我;我在Twitter上联系了Dareboost,但尚未收到他们的回复。
编辑(2021/02/10):Dareboost此后修改了他们的文章。
记住:SameSite是保护用户免受跨站攻击的强大深度防御机制,但它对跨源、同站攻击无能为力。不要错过这个细微差别!否则,如果你在防御方,你可能会被麻痹 into a false sense of security,并且你可能被你认为不可能的攻击 blindsided;如果你在攻击方,你可能会错过漏洞发现,甚至可能错过可观的漏洞赏金。
附录(2021/01/31)
自从本文首次发布以来,我发现了更多在描述SameSite机制时不谨慎使用“源”和“站点”术语的值得注意的实例。我没有尝试联系下面引用的文章的作者。
早在2016年,Qbit Cyber Security的Web应用程序黑客Sjoerd Langkemper在他的博客上写道:
此表显示了跨源请求发送哪些Cookie。如你所见,没有相同站点属性的Cookie […] 总是被发送。Strict Cookie从不被发送。Lax Cookie仅随顶级get请求发送。(我的强调)
2018年5月,Artur Janc和Mike West(Same-site Cookies Internet Draft的作者本人)发布了一份报告(PDF),题为“How do we Stop Spilling the Beans Across Origins?”,其中你可以读到:
SameSite Cookie不直接防止攻击者加载跨源资源,但它们导致此类请求在没有凭据的情况下发送,使响应对攻击者几乎没有价值。(我的强调)
最后,关于CSRF的维基百科页面本身声称:
如果此属性设置为“strict”,则Cookie仅在同源请求上发送,使CSRF无效。(我的强调)
编辑(2021/02/07):维基百科页面此后已被更正。
致谢
我要感谢Detectify的Fredrik N. Almroth,他 kindly 同意在发布前审阅本文的草稿。