searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Cookie SameSite属性详解

2023-06-02 09:23:07
47
0

我们都知道,服务端在设置 cookie 的时候,除了 cookie 的键和值以外,还可以同时给 cookie 设置一些属性,例如:Expires,Max-Age,Domain,Path,Secure,HttpOnly,SameSite。开发者通过这些属性来告知浏览器在什么时候,什么情况下使用该 cookie。其中的 SameSite 属性就是本文要讨论的主角,后面的内容主要包含以下三点:SameSite 属性基础,SameSite 属性的演进,SameSite 属性带来的影响

SameSite 属性基础

在说 SameSite 属性之前,我们需要先了解一下 SameSite 里的 site 指的是什么,以及什么是同站请求?什么是跨站请求?

site 的含义

首先要知道:有效顶级域名(eTLD, effective top-level domain)对应的是由 Mozilla 维护的公共后缀列表[Public Suffix List](https://publicsuffix.org/)里包含的域名。这个列表由两部分组成:
一部分是由域名注册机构提供的顶级域名(例如:.com,.net 等)和部分二级域名(例如:`.http://gov.uk`,`.http://org.uk` 等)。
另一部分是由个人或机构提供的私有域名,例如:(`github.io`,`compute.amazonaws.com`)等。而 SameSite 里的 site 指的是 eTLD+1,即:有效顶级域名再加上它的下一级域名。举例说明:
- `http://qzone.qq.com` 对应的 site 是 `http://qq.com`。它的 eTLD 是 .com,eTLD+1 就是 `http://qq.com`
- `http://vip.qzone.qq.com` 对应的 site 也是 `http://qq.com`。它的 eTLD 是 .com,eTLD+1 也是 `http://qq.com`
- `http://bootstrap.github.io` 对应的 site 是 `http://bootstrap.github.io` 而不是 `http://github.io`。它的 eTLD 是 `http://github.io`,eTLD+1 是 `http://bootstrap.github.io`

同站 (same-site) 请求 VS 跨站 (cross-site) 请求

一个 HTML 页面既可以发起同站请求,也可以发起跨站请求。当请求目标的 URL 对应的 site 与页面所在 URL 对应的 site 相同时,这个请求就是同站请求,反之就是跨站请求。
例如:
当 `http://www.baidu.com` 的网页,请求 `http://static.baidu.com`域下的图片,这个请求属于同站请求
当 `http://a.github.io` 的网页,请求 `http://b.github.io` 域下的图片,这个请求属于跨站请求
这里要注意和同源策略里的 same origin 做一下区分。同源指的是同协议、同域名、同端口。同站只看 site 是否一致,不管协议和端口。所以同源一定同站,同站不一定同源。

跨域的几种方法总结

设置domain

  • 原理:相同主域名不同子域名下的页面,可以设置document.domain让它们同域
  • 限制:同域document提供的是页面间的互操作,需要载入iframe页面
  • document.domain只能从子域设置到主域,往下设置以及往其他域名设置都是不允许的
  • 端口号是由浏览器另行检查的。任何对document.domain的赋值操作,包括 document.domain = document.domain 都会导致端口号被重写为 null 。因此 company.com:8080 不能仅通过设置 document.domain = "company.com" 来与company.com 通信。必须在他们双方中都进行赋值,以确保端口号都为 null 。

跨域资源嵌入-有src的标签

  • 原理:所有具有src属性的HTML标签都是可以跨域的,包括`<img>, <script>,<link>`
  • 限制:需要创建一个DOM对象,只能用于GET方法
  • 在document.body中append一个具有src属性的HTML标签, src属性值指向的URL会以GET方法被访问,该访问是可以跨域
  • 不同的HTML标签发送HTTP请求的时机不同,例如`<img>`在更改src属性时就会发送请求,而`script, iframe, link[rel=stylesheet]`只有在添加到DOM树之后才会发送HTTP请求
  • 一些浏览器允许跨域字体(cross-origin fonts),一些需要同源字体(same-origin fonts)
  • iframe 站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。

JSONP

  • 原理:`<script>`是可以跨域的,而且在跨域脚本中可以直接回调当前脚本的函数。
  • 限制:需要创建一个DOM对象并且添加到DOM树,只能用于GET方法
  • 虽然`[RFC 2616][RFC 2610]`没有提到限制到多少, 但提到了服务器可以对自己认为比较长的URL返回414状态码。一般来讲URL限长是在2000字符左右。

postMessage

  • 原理:HTML5允许窗口之间发送消息
  • 限制:浏览器需要支持HTML5,获取窗口句柄后才能相互通信

navigation 对象

  • iframe之间是共享navigator对象的,用它来传递信息
  • 要求:IE6/7,有些人注意到了IE6/7的一个漏洞:iframe之间的window.navigator对象是共享的。 我们可以把它作为一个Messenger,通过它来传递信息

SameSite 属性

SameSite 属性用来控制 HTTP 请求携带何种 cookie。这是通过它的三种值来实现:None,Lax,Strict
SameSite 属性可以用在 HTTP 响应头里:Set-Cookie: sessionId=F123ABCA; SameSite=Strict; secure; httponly;
也可以在 JS 代码里使用:document.cookie= "sessionId=F123ABCA; SameSite=Strict; secure;"
使用的时候,SameSite 关键字和它的三个值都不区分大小写。
对于 SameSite=Strict 的 cookie:只有同站请求会携带此类 cookie。
对于 SameSite=None 的 cookie:同站请求和跨站请求都会携带此类 cookie。
Lax 的行为介于 None 和 Strict 之间。对于 SameSite=Lax 的 cookie,除了同站请求会携带此类 cookie 之外,特定情况的跨站请求也会携带此类 cookie。
特定情况的跨站请求指的是:safe cross-site top-level navigations(后文简称:安全的跨站顶级跳转)
- 点击超链接产生的请求
- 以 GET 方法提交表单产生的请求
- JS 修改 location 对象产生的跳转请求
- JS 调用 window.open() 等方式产生的跳转请求
反过来,哪些跨站顶级跳转是不安全的呢?例如:以 POST 方法提交表单产生的请求
通过不同方式发起跨站请求,cookie 发送情况可以简单总结为下表:
![](../img/samesite.jpg)

SameSite 的演进

SameSite 的出现

在 cookie 最初的规范 [RFC 6265](https://tools.ietf.org/html/rfc6265) 里是没有 SameSite 属性的。直到2016年,`https://tools.ietf.org/html/draft-west-first-party-cookies-05` SameSite属性才被提出。

Cookie 的改进

cookie 最初的行为是:无论是同站请求还是跨站请求都会带上各自域下的 cookie,效果等同于SameSite=None。
这样的行为导致了一些安全和隐私上的问题:CSRF 漏洞,跨域信息泄露。
为了解决这些问题,出了一个新提案:Incrementally Better Cookies(后文简称 [IBC](https://tools.ietf.org/html/draft-west-cookie-incrementalism-00)),里面提出了两点改进:
- 没有声明 SameSite 属性的cookie 被处理为 SameSite=Lax。换句话说:cookie 的默认行为由 SameSite=None 改为 SameSite=Lax
- 设置为 SameSite=None 的 cookie,必须同时被标记为 Secure。换句话说:只能在 HTTPS 的情况下使用 SameSite=None
withCredentials属性值和samesite值会出现冲突,此时谁的权重更高呢?此时以samesite为主

动手实践

如果你想查看自己的浏览器是否已经完整启用 IBC,可以访问:[https://samesite-sandbox.glitch.me](https://samesite-sandbox.glitch.me) 进行测试。
测试页面的运行逻辑如下:
- 访问主页面`https://samesite-sandbox.glitch.me`,响应头里设置了 6 个 cookie
- 主页面中包含一个iframe,指向一个跨域页面:`https://googlechromelabs.github.io`
- 该跨域页面向主页面所在域名http://samesite-sandbox.glitch.me 发起一个 Ajax 请求,同时指定 withCredentials=true
- 这个Ajax 在 iframe 里属于跨站请求,会携带部分 cookie,后端返回的响应就是请求中携带的 cookie 列表
- iframe 接收到 Ajax 响应后,通过postMessage 把结果传递给主页面,用来告知主页面:跨站请求携带了哪些 cookie。`window.parent.postMessage(jsonResponse,"https://samesite-sandbox.glitch.me")`
- 主页面通过比对 cookie 的设置情况和跨站请求 cookie 的发送情况,给出兼容性表格。

SameSite 带来的影响

浏览器启用 IBC 带来的变化就是:除了安全的跨站顶级跳转之外的跨站请求默认都不会携带 cookie,除非显式的将 cookie 设置成 SameSite=None。乍一看,这个变动似乎不大,但实际上它的影响范围并不小,尤其是对于那些在跨站上下文中使用 cookie 的场景。接下来,分别从攻击者和开发者的角度进行简单分析。

从攻击者的角度

在所有 cookie 里,攻击者更关注的其实是用来维持用户登录状态的 session cookie。如果攻击者发起的请求没有携带对应用户的 session cookie,那么网站会将其判定为未登录状态,这就导致那些需要登录才能访问的数据会获取不到,需要登录才能执行的操作会无法进行。

我在本地准备两个测试站点:
http://www.foo.com:3000模拟存在漏洞的网站(方便起见,后文描述时省略端口号)
http://www.evil.com:4000模拟攻击者的网站
用户在http://www.foo.com登录之后,会得到一个没有设置 SameSite 属性 session cookie。不设置 SameSite 属性为的是让 cookie 使用 SameSite 默认值,而且,这也和当前大多数网站的行为一致,后文的讨论也是基于这种设定。
1. CSRF
CSRF 攻击依赖的就是跨站请求会自动带上用户 cookie,进而可以伪造请求,代替用户执行敏感操作。
测试站点 http://www.foo.com 有一个修改邮箱的接口,存在 CSRF 漏洞。`<form action="https://www.foo.com:3000/foo/change_email" method="post"> <input type="text" name="email"> <button>change email</button></form>` 在未启用 IBC 时,可以看到伪造的请求,携带了 cookie,邮箱成功被修改。启用 IBC 后,可以看到伪造的请求没有携带 cookie,后端响应提示需要登录。CSRF 利用失败。把 method 改为 get 再次测试,Burp 拦截到的请求如下图所示:伪造的请求携带了 cookie(因为 GET 形式提交表单属于安全的跨站顶级跳转),邮箱成功被修改。可以看到,IBC 对 POST 形式的 CSRF 漏洞可以起到很好的防御效果。对于前面提到的浏览器实现中的特例:Lax + POST,也有研究人员总结了一些利用方式`https://medium.com/@renwa/bypass-samesite-cookies-default-to-lax-and-get-csrf-343ba09b9f2b`
2. XSSI(Cross-Site Script Inclusion)
Cross-Site Script Inclusion 是一种允许攻击者跨域窃取特定类型数据的攻击技术。
回想一下,我们经常能看到某个站点的 HTML 页面里用 script 标签引入了外站的 JS 文件,之所以可以这样做,是因为同源策略并不会限制这种行为。攻击者利用的同样也是这一点,构造一个恶意页面,直接用 script 标签引入包含受害者隐私数据的跨域资源,当受害者访问恶意页面时,浏览器就会自动请求对应资源,同时会带上相关 cookie,这样恶意页面就拿到了受害者的隐私数据。具体的可以参考:[CROSS-SITE SCRIPT INCLUSION - A FAMELESS BUT WIDESPREAD WEB VULNERABILITY CLASS](https://www.scip.ch/en/?labs.20160414)启用 IBC 之后,session cookie 的 SameSite 属性默认值变为 Lax,恶意页面跨域请求包含受害者隐私数据的资源时不再携带 session cookie,相当于以未登录状态访问隐私数据,肯定会失败。
3. CSWSH(Cross-Site WebSocket Hijacking)
Cross-Site WebSocket Hijacking 利用的是 WebSocket 不受同源策略限制,当用户访问恶意网页时,恶意网页可以向目标网站发起一个 WebSocket 连接,第一个握手请求就是正常 HTTP(S) 请求,会带上用户的认证信息(session cookie 等),如果开发者没有检测握手请求的 Origin,而是仅仅通过 session cookie 对用户进行认证并允许建立 WebSocket 连接,那么攻击者就可以进行 Cross-Site WebSocket Hijacking。更详细的原理可以参考:[Cross-Site WebSocket Hijacking](https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html)。启用 IBC 之后,session cookie 的 SameSite 属性默认值变为 Lax,跨站握手请求不携带 session cookie,也就无法像上面一样成功建立 WebSocket 连接,导致 Cross-Site WebSocket Hijacking 失败。
4. XSLeaks
XSLeaks 和 XSSI 类似,都是用来跨域获取受害者的隐私数据,只不过 XSLeaks 利用的是浏览器的 side channel,例如:HTTP 响应的耗时等等。具体的可以参考:`https://github.com/xsleaks/xsleaks/` 启用 IBC 之后,session cookie 的 SameSite 属性默认值变为 Lax,用来探测用户隐私数据的跨域请求不携带 session cookie,导致无法获取那些需要登录才能访问的隐私数据。但是需要指出的是,一些特定的侧信道技术,例如:通过 window.open() 的,可能依然有效,因为这属于安全的跨站顶级跳转。
5. Data Exfiltration
这里的 Data Exfiltration 指的是利用不同技术手段绕过同源策略进而提取目标数据。例如:
- CVE-2015-1287、CVE-2015-5826 通过 CSS 跨域提取数据(https://blog.innerht.ml/cross-origin-css-attacks-revisited-feat-utf-16/)
- CVE-2014-3160 通过 SVG 绕过 Chrome 同源策略(https://christian-schneider.net/ChromeSopBypassWithSvg.html)
Data Exfiltration 成功的前提也是跨域请求自动携带 session cookie,进而提取隐私数据。启用 IBC 后,这个前提不再成立,攻击也就不再有效。
6. CORS(Cross-Origin Resource Sharing)配置错误
CORS 允许 Web 应用服务器进行跨域访问控制,服务器通过响应头来控制哪些来源的请求可以访问自身资源,从而使跨域数据传输可以安全进行。但是从另一个角度看:CORS 相当于在同源策略这堵墙上开了一扇窗。一旦开发者没有正确配置 CORS 响应头,就会让攻击者有机可乘。常见的错误配置有:
- 开发者未做任何验证,直接把请求头里的 Origin 原样输出到了 Access-Control-Allow-Origin 响应头
- 开发者使用正则表达式对 Origin 进行判断后将其输出到 Access-Control-Allow-Origin 响应头,但是正则表达式写的不完备,存在绕过
- 把 Access-Control-Allow-Origin 响应头的值设置成 null
如果出现上述错误配置,攻击者就可以跨域发起请求,并携带认证 cookie,访问目标资源。
开发者使用 CORS 和 JSONP 的目的都是为了跨站数据传输。IBC 对二者的影响也类似,启用 IBC 后,要使 CORS 正常工作,需要显式的将跨站请求用到的 cookie 设置为 SameSite=None。所以 IBC 对 CORS 配置错误漏洞影响不大。
7. XSS
IBC 对 XSS 漏洞本身影响不大,影响的主要是一些利用方式,比如常见的:通过 iframe 嵌入目标网站来触发其 XSS。`<iframe src="https://www.foo.com:3000/foo/profile?name=%3Cimg/src=x+onerror=alert(document.cookie)%3E" frameborder="0"></iframe`。点击劫持成功的前提是:能够在攻击者的页面中嵌入目标网站,同时受害者在目标网站是已登录状态。和上面通过 iframe 触发 XSS 一样,由于嵌入目标网站的请求属于跨站请求,启用 IBC 之后,session cookie 的 SameSite 属性默认值变为 Lax,跨站请求不携带 session cookie,导致实际嵌进来的页面是未登录状态,也就没办法进行敏感操作,点击劫持失去意义。需要指出的是,如果目标网站把认证信息 session ID,access tokens 之类的保存到了本地存储(localStorage 或是 sessionStorage),并不依赖 cookie,那么点击劫持依然有效。
8. JSONP 泄露
JSONP 是一种跨域通信机制。可以把 JSONP 泄露看成是 XSSI 攻击的一种形式。
先自定义一个用于接受数据的 evil 函数,然后把函数名通过 cb 参数传递给 JSONP 接口`<script> function evil(data) { console.log(data); }</script><script src="https://www.foo.com:3000/foo/jsonp?cb=evil"></script>`在未启用 IBC 时,JSONP 请求携带了 cookie,服务端正常返回了邮箱。启用 IBC 后,JSONP 请求未携带 cookie,服务端提示需要登录。但是这时候,不仅恶意网站无法利用 JSONP 接口窃取数据,就连 JSONP 接口原本的使用方也无法正常获取数据了。所以,为了使 JSONP 接口正常工作,开发者需要显式的将相关 cookie 设置为 SameSite=None。把 sessionId cookie 的 SameSite 设置成 None 后再次测试:JSONP 泄露又可以被利用了。
![](../img/samesite_1.jpg)
需要说明的是:不要把上面的讨论当成结论,具体漏洞还需要具体分析。不同漏洞的变种、利用条件、组合方式的差异可能会带来不一样的结果。举个例子:假设攻击者的目标站点是 http://www.foo.com, 如果攻击者能够通过某种方式(脚本注入,甚至只是一个 HTML 注入)绕过同源策略,进入目标站点的兄弟域或是子域的上下文,那么也就相当于进入了同站的范围内,此时 SameSite 属性就起不到任何限制了。
![](../img/samesite_2.jpg

从开发者的角度

各个 cookie 的 SameSite 要使用哪个值,需要根据具体业务、使用场景来进行选择,同时还要考虑安全性和用户体验。
简单的,如果只想将 cookie 的访问限制在自己的网站里,应该选择使用 SameSite=Strict 来阻止跨站使用。
但是全部使用 SameSite=Strict,可能会带来一些用户体验上的问题。例如:著名社区土司比较注重用户的安全,所以将整站的 Cookie 都设置成了 SameSite=Strict。但这样会导致很多从外站点击超链接跳转到土司的用户无法正常查看帖子,可能需要重新登录。
所以,如果你希望从其它网站(例如:百度、邮箱)通过点击超链接跳转过来的用户的登录状态不丢失,就需要考虑使用 SameSite=Lax。
如果你的网站需要和其它网站在前端进行数据交换,或者深度融合(可以嵌入到其它网站),那么就得考虑使用 SameSite=None。
使用 SameSite=None 时还要注意,有一些客户端并不支持或存在 Bug,例如:旧版本的 Safari 会把 SameSite=None 当做 SameSite=Strict 来处理。

0条评论
0 / 1000
y****n
2文章数
0粉丝数
y****n
2 文章 | 0 粉丝
y****n
2文章数
0粉丝数
y****n
2 文章 | 0 粉丝
原创

Cookie SameSite属性详解

2023-06-02 09:23:07
47
0

我们都知道,服务端在设置 cookie 的时候,除了 cookie 的键和值以外,还可以同时给 cookie 设置一些属性,例如:Expires,Max-Age,Domain,Path,Secure,HttpOnly,SameSite。开发者通过这些属性来告知浏览器在什么时候,什么情况下使用该 cookie。其中的 SameSite 属性就是本文要讨论的主角,后面的内容主要包含以下三点:SameSite 属性基础,SameSite 属性的演进,SameSite 属性带来的影响

SameSite 属性基础

在说 SameSite 属性之前,我们需要先了解一下 SameSite 里的 site 指的是什么,以及什么是同站请求?什么是跨站请求?

site 的含义

首先要知道:有效顶级域名(eTLD, effective top-level domain)对应的是由 Mozilla 维护的公共后缀列表[Public Suffix List](https://publicsuffix.org/)里包含的域名。这个列表由两部分组成:
一部分是由域名注册机构提供的顶级域名(例如:.com,.net 等)和部分二级域名(例如:`.http://gov.uk`,`.http://org.uk` 等)。
另一部分是由个人或机构提供的私有域名,例如:(`github.io`,`compute.amazonaws.com`)等。而 SameSite 里的 site 指的是 eTLD+1,即:有效顶级域名再加上它的下一级域名。举例说明:
- `http://qzone.qq.com` 对应的 site 是 `http://qq.com`。它的 eTLD 是 .com,eTLD+1 就是 `http://qq.com`
- `http://vip.qzone.qq.com` 对应的 site 也是 `http://qq.com`。它的 eTLD 是 .com,eTLD+1 也是 `http://qq.com`
- `http://bootstrap.github.io` 对应的 site 是 `http://bootstrap.github.io` 而不是 `http://github.io`。它的 eTLD 是 `http://github.io`,eTLD+1 是 `http://bootstrap.github.io`

同站 (same-site) 请求 VS 跨站 (cross-site) 请求

一个 HTML 页面既可以发起同站请求,也可以发起跨站请求。当请求目标的 URL 对应的 site 与页面所在 URL 对应的 site 相同时,这个请求就是同站请求,反之就是跨站请求。
例如:
当 `http://www.baidu.com` 的网页,请求 `http://static.baidu.com`域下的图片,这个请求属于同站请求
当 `http://a.github.io` 的网页,请求 `http://b.github.io` 域下的图片,这个请求属于跨站请求
这里要注意和同源策略里的 same origin 做一下区分。同源指的是同协议、同域名、同端口。同站只看 site 是否一致,不管协议和端口。所以同源一定同站,同站不一定同源。

跨域的几种方法总结

设置domain

  • 原理:相同主域名不同子域名下的页面,可以设置document.domain让它们同域
  • 限制:同域document提供的是页面间的互操作,需要载入iframe页面
  • document.domain只能从子域设置到主域,往下设置以及往其他域名设置都是不允许的
  • 端口号是由浏览器另行检查的。任何对document.domain的赋值操作,包括 document.domain = document.domain 都会导致端口号被重写为 null 。因此 company.com:8080 不能仅通过设置 document.domain = "company.com" 来与company.com 通信。必须在他们双方中都进行赋值,以确保端口号都为 null 。

跨域资源嵌入-有src的标签

  • 原理:所有具有src属性的HTML标签都是可以跨域的,包括`<img>, <script>,<link>`
  • 限制:需要创建一个DOM对象,只能用于GET方法
  • 在document.body中append一个具有src属性的HTML标签, src属性值指向的URL会以GET方法被访问,该访问是可以跨域
  • 不同的HTML标签发送HTTP请求的时机不同,例如`<img>`在更改src属性时就会发送请求,而`script, iframe, link[rel=stylesheet]`只有在添加到DOM树之后才会发送HTTP请求
  • 一些浏览器允许跨域字体(cross-origin fonts),一些需要同源字体(same-origin fonts)
  • iframe 站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互。

JSONP

  • 原理:`<script>`是可以跨域的,而且在跨域脚本中可以直接回调当前脚本的函数。
  • 限制:需要创建一个DOM对象并且添加到DOM树,只能用于GET方法
  • 虽然`[RFC 2616][RFC 2610]`没有提到限制到多少, 但提到了服务器可以对自己认为比较长的URL返回414状态码。一般来讲URL限长是在2000字符左右。

postMessage

  • 原理:HTML5允许窗口之间发送消息
  • 限制:浏览器需要支持HTML5,获取窗口句柄后才能相互通信

navigation 对象

  • iframe之间是共享navigator对象的,用它来传递信息
  • 要求:IE6/7,有些人注意到了IE6/7的一个漏洞:iframe之间的window.navigator对象是共享的。 我们可以把它作为一个Messenger,通过它来传递信息

SameSite 属性

SameSite 属性用来控制 HTTP 请求携带何种 cookie。这是通过它的三种值来实现:None,Lax,Strict
SameSite 属性可以用在 HTTP 响应头里:Set-Cookie: sessionId=F123ABCA; SameSite=Strict; secure; httponly;
也可以在 JS 代码里使用:document.cookie= "sessionId=F123ABCA; SameSite=Strict; secure;"
使用的时候,SameSite 关键字和它的三个值都不区分大小写。
对于 SameSite=Strict 的 cookie:只有同站请求会携带此类 cookie。
对于 SameSite=None 的 cookie:同站请求和跨站请求都会携带此类 cookie。
Lax 的行为介于 None 和 Strict 之间。对于 SameSite=Lax 的 cookie,除了同站请求会携带此类 cookie 之外,特定情况的跨站请求也会携带此类 cookie。
特定情况的跨站请求指的是:safe cross-site top-level navigations(后文简称:安全的跨站顶级跳转)
- 点击超链接产生的请求
- 以 GET 方法提交表单产生的请求
- JS 修改 location 对象产生的跳转请求
- JS 调用 window.open() 等方式产生的跳转请求
反过来,哪些跨站顶级跳转是不安全的呢?例如:以 POST 方法提交表单产生的请求
通过不同方式发起跨站请求,cookie 发送情况可以简单总结为下表:
![](../img/samesite.jpg)

SameSite 的演进

SameSite 的出现

在 cookie 最初的规范 [RFC 6265](https://tools.ietf.org/html/rfc6265) 里是没有 SameSite 属性的。直到2016年,`https://tools.ietf.org/html/draft-west-first-party-cookies-05` SameSite属性才被提出。

Cookie 的改进

cookie 最初的行为是:无论是同站请求还是跨站请求都会带上各自域下的 cookie,效果等同于SameSite=None。
这样的行为导致了一些安全和隐私上的问题:CSRF 漏洞,跨域信息泄露。
为了解决这些问题,出了一个新提案:Incrementally Better Cookies(后文简称 [IBC](https://tools.ietf.org/html/draft-west-cookie-incrementalism-00)),里面提出了两点改进:
- 没有声明 SameSite 属性的cookie 被处理为 SameSite=Lax。换句话说:cookie 的默认行为由 SameSite=None 改为 SameSite=Lax
- 设置为 SameSite=None 的 cookie,必须同时被标记为 Secure。换句话说:只能在 HTTPS 的情况下使用 SameSite=None
withCredentials属性值和samesite值会出现冲突,此时谁的权重更高呢?此时以samesite为主

动手实践

如果你想查看自己的浏览器是否已经完整启用 IBC,可以访问:[https://samesite-sandbox.glitch.me](https://samesite-sandbox.glitch.me) 进行测试。
测试页面的运行逻辑如下:
- 访问主页面`https://samesite-sandbox.glitch.me`,响应头里设置了 6 个 cookie
- 主页面中包含一个iframe,指向一个跨域页面:`https://googlechromelabs.github.io`
- 该跨域页面向主页面所在域名http://samesite-sandbox.glitch.me 发起一个 Ajax 请求,同时指定 withCredentials=true
- 这个Ajax 在 iframe 里属于跨站请求,会携带部分 cookie,后端返回的响应就是请求中携带的 cookie 列表
- iframe 接收到 Ajax 响应后,通过postMessage 把结果传递给主页面,用来告知主页面:跨站请求携带了哪些 cookie。`window.parent.postMessage(jsonResponse,"https://samesite-sandbox.glitch.me")`
- 主页面通过比对 cookie 的设置情况和跨站请求 cookie 的发送情况,给出兼容性表格。

SameSite 带来的影响

浏览器启用 IBC 带来的变化就是:除了安全的跨站顶级跳转之外的跨站请求默认都不会携带 cookie,除非显式的将 cookie 设置成 SameSite=None。乍一看,这个变动似乎不大,但实际上它的影响范围并不小,尤其是对于那些在跨站上下文中使用 cookie 的场景。接下来,分别从攻击者和开发者的角度进行简单分析。

从攻击者的角度

在所有 cookie 里,攻击者更关注的其实是用来维持用户登录状态的 session cookie。如果攻击者发起的请求没有携带对应用户的 session cookie,那么网站会将其判定为未登录状态,这就导致那些需要登录才能访问的数据会获取不到,需要登录才能执行的操作会无法进行。

我在本地准备两个测试站点:
http://www.foo.com:3000模拟存在漏洞的网站(方便起见,后文描述时省略端口号)
http://www.evil.com:4000模拟攻击者的网站
用户在http://www.foo.com登录之后,会得到一个没有设置 SameSite 属性 session cookie。不设置 SameSite 属性为的是让 cookie 使用 SameSite 默认值,而且,这也和当前大多数网站的行为一致,后文的讨论也是基于这种设定。
1. CSRF
CSRF 攻击依赖的就是跨站请求会自动带上用户 cookie,进而可以伪造请求,代替用户执行敏感操作。
测试站点 http://www.foo.com 有一个修改邮箱的接口,存在 CSRF 漏洞。`<form action="https://www.foo.com:3000/foo/change_email" method="post"> <input type="text" name="email"> <button>change email</button></form>` 在未启用 IBC 时,可以看到伪造的请求,携带了 cookie,邮箱成功被修改。启用 IBC 后,可以看到伪造的请求没有携带 cookie,后端响应提示需要登录。CSRF 利用失败。把 method 改为 get 再次测试,Burp 拦截到的请求如下图所示:伪造的请求携带了 cookie(因为 GET 形式提交表单属于安全的跨站顶级跳转),邮箱成功被修改。可以看到,IBC 对 POST 形式的 CSRF 漏洞可以起到很好的防御效果。对于前面提到的浏览器实现中的特例:Lax + POST,也有研究人员总结了一些利用方式`https://medium.com/@renwa/bypass-samesite-cookies-default-to-lax-and-get-csrf-343ba09b9f2b`
2. XSSI(Cross-Site Script Inclusion)
Cross-Site Script Inclusion 是一种允许攻击者跨域窃取特定类型数据的攻击技术。
回想一下,我们经常能看到某个站点的 HTML 页面里用 script 标签引入了外站的 JS 文件,之所以可以这样做,是因为同源策略并不会限制这种行为。攻击者利用的同样也是这一点,构造一个恶意页面,直接用 script 标签引入包含受害者隐私数据的跨域资源,当受害者访问恶意页面时,浏览器就会自动请求对应资源,同时会带上相关 cookie,这样恶意页面就拿到了受害者的隐私数据。具体的可以参考:[CROSS-SITE SCRIPT INCLUSION - A FAMELESS BUT WIDESPREAD WEB VULNERABILITY CLASS](https://www.scip.ch/en/?labs.20160414)启用 IBC 之后,session cookie 的 SameSite 属性默认值变为 Lax,恶意页面跨域请求包含受害者隐私数据的资源时不再携带 session cookie,相当于以未登录状态访问隐私数据,肯定会失败。
3. CSWSH(Cross-Site WebSocket Hijacking)
Cross-Site WebSocket Hijacking 利用的是 WebSocket 不受同源策略限制,当用户访问恶意网页时,恶意网页可以向目标网站发起一个 WebSocket 连接,第一个握手请求就是正常 HTTP(S) 请求,会带上用户的认证信息(session cookie 等),如果开发者没有检测握手请求的 Origin,而是仅仅通过 session cookie 对用户进行认证并允许建立 WebSocket 连接,那么攻击者就可以进行 Cross-Site WebSocket Hijacking。更详细的原理可以参考:[Cross-Site WebSocket Hijacking](https://www.christian-schneider.net/CrossSiteWebSocketHijacking.html)。启用 IBC 之后,session cookie 的 SameSite 属性默认值变为 Lax,跨站握手请求不携带 session cookie,也就无法像上面一样成功建立 WebSocket 连接,导致 Cross-Site WebSocket Hijacking 失败。
4. XSLeaks
XSLeaks 和 XSSI 类似,都是用来跨域获取受害者的隐私数据,只不过 XSLeaks 利用的是浏览器的 side channel,例如:HTTP 响应的耗时等等。具体的可以参考:`https://github.com/xsleaks/xsleaks/` 启用 IBC 之后,session cookie 的 SameSite 属性默认值变为 Lax,用来探测用户隐私数据的跨域请求不携带 session cookie,导致无法获取那些需要登录才能访问的隐私数据。但是需要指出的是,一些特定的侧信道技术,例如:通过 window.open() 的,可能依然有效,因为这属于安全的跨站顶级跳转。
5. Data Exfiltration
这里的 Data Exfiltration 指的是利用不同技术手段绕过同源策略进而提取目标数据。例如:
- CVE-2015-1287、CVE-2015-5826 通过 CSS 跨域提取数据(https://blog.innerht.ml/cross-origin-css-attacks-revisited-feat-utf-16/)
- CVE-2014-3160 通过 SVG 绕过 Chrome 同源策略(https://christian-schneider.net/ChromeSopBypassWithSvg.html)
Data Exfiltration 成功的前提也是跨域请求自动携带 session cookie,进而提取隐私数据。启用 IBC 后,这个前提不再成立,攻击也就不再有效。
6. CORS(Cross-Origin Resource Sharing)配置错误
CORS 允许 Web 应用服务器进行跨域访问控制,服务器通过响应头来控制哪些来源的请求可以访问自身资源,从而使跨域数据传输可以安全进行。但是从另一个角度看:CORS 相当于在同源策略这堵墙上开了一扇窗。一旦开发者没有正确配置 CORS 响应头,就会让攻击者有机可乘。常见的错误配置有:
- 开发者未做任何验证,直接把请求头里的 Origin 原样输出到了 Access-Control-Allow-Origin 响应头
- 开发者使用正则表达式对 Origin 进行判断后将其输出到 Access-Control-Allow-Origin 响应头,但是正则表达式写的不完备,存在绕过
- 把 Access-Control-Allow-Origin 响应头的值设置成 null
如果出现上述错误配置,攻击者就可以跨域发起请求,并携带认证 cookie,访问目标资源。
开发者使用 CORS 和 JSONP 的目的都是为了跨站数据传输。IBC 对二者的影响也类似,启用 IBC 后,要使 CORS 正常工作,需要显式的将跨站请求用到的 cookie 设置为 SameSite=None。所以 IBC 对 CORS 配置错误漏洞影响不大。
7. XSS
IBC 对 XSS 漏洞本身影响不大,影响的主要是一些利用方式,比如常见的:通过 iframe 嵌入目标网站来触发其 XSS。`<iframe src="https://www.foo.com:3000/foo/profile?name=%3Cimg/src=x+onerror=alert(document.cookie)%3E" frameborder="0"></iframe`。点击劫持成功的前提是:能够在攻击者的页面中嵌入目标网站,同时受害者在目标网站是已登录状态。和上面通过 iframe 触发 XSS 一样,由于嵌入目标网站的请求属于跨站请求,启用 IBC 之后,session cookie 的 SameSite 属性默认值变为 Lax,跨站请求不携带 session cookie,导致实际嵌进来的页面是未登录状态,也就没办法进行敏感操作,点击劫持失去意义。需要指出的是,如果目标网站把认证信息 session ID,access tokens 之类的保存到了本地存储(localStorage 或是 sessionStorage),并不依赖 cookie,那么点击劫持依然有效。
8. JSONP 泄露
JSONP 是一种跨域通信机制。可以把 JSONP 泄露看成是 XSSI 攻击的一种形式。
先自定义一个用于接受数据的 evil 函数,然后把函数名通过 cb 参数传递给 JSONP 接口`<script> function evil(data) { console.log(data); }</script><script src="https://www.foo.com:3000/foo/jsonp?cb=evil"></script>`在未启用 IBC 时,JSONP 请求携带了 cookie,服务端正常返回了邮箱。启用 IBC 后,JSONP 请求未携带 cookie,服务端提示需要登录。但是这时候,不仅恶意网站无法利用 JSONP 接口窃取数据,就连 JSONP 接口原本的使用方也无法正常获取数据了。所以,为了使 JSONP 接口正常工作,开发者需要显式的将相关 cookie 设置为 SameSite=None。把 sessionId cookie 的 SameSite 设置成 None 后再次测试:JSONP 泄露又可以被利用了。
![](../img/samesite_1.jpg)
需要说明的是:不要把上面的讨论当成结论,具体漏洞还需要具体分析。不同漏洞的变种、利用条件、组合方式的差异可能会带来不一样的结果。举个例子:假设攻击者的目标站点是 http://www.foo.com, 如果攻击者能够通过某种方式(脚本注入,甚至只是一个 HTML 注入)绕过同源策略,进入目标站点的兄弟域或是子域的上下文,那么也就相当于进入了同站的范围内,此时 SameSite 属性就起不到任何限制了。
![](../img/samesite_2.jpg

从开发者的角度

各个 cookie 的 SameSite 要使用哪个值,需要根据具体业务、使用场景来进行选择,同时还要考虑安全性和用户体验。
简单的,如果只想将 cookie 的访问限制在自己的网站里,应该选择使用 SameSite=Strict 来阻止跨站使用。
但是全部使用 SameSite=Strict,可能会带来一些用户体验上的问题。例如:著名社区土司比较注重用户的安全,所以将整站的 Cookie 都设置成了 SameSite=Strict。但这样会导致很多从外站点击超链接跳转到土司的用户无法正常查看帖子,可能需要重新登录。
所以,如果你希望从其它网站(例如:百度、邮箱)通过点击超链接跳转过来的用户的登录状态不丢失,就需要考虑使用 SameSite=Lax。
如果你的网站需要和其它网站在前端进行数据交换,或者深度融合(可以嵌入到其它网站),那么就得考虑使用 SameSite=None。
使用 SameSite=None 时还要注意,有一些客户端并不支持或存在 Bug,例如:旧版本的 Safari 会把 SameSite=None 当做 SameSite=Strict 来处理。

文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0