自Chrome 51版本开始,浏览器的 Cookies 新增了一个SameSite
属性,用来防止 CSRF 攻击和信息泄漏。
简单回顾什么是CSRF攻击
Cookies
往往用来存储用户的身份信息,恶意网站通过设法伪造带有正确Cookies
进行 HTTP 请求,这就是 CSRF 攻击。
举例来说,用户登陆了银行网站your-bank.com
,银行服务器发来了一个 Cookie。
Set-Cookie: session_id=abc123;
用户后来又访问了恶意网站malicious-site.com
,恶意网站总是想方设法让你在恶意站点发送一个表单请求。
手段:中奖填写联系信息、透明的form表单提交按钮、附加在诱惑图片上的超链接
<form action="your-bank.com/transfer" method="POST">
...
</form>
用户一旦被诱骗发送这个表单,银行网站就会收到带有正确 Cookie 的请求。为了防止CSRF攻击,银行网站表单会设置一个隐藏域,表单提交时一起带上一个随机token至服务器,告诉服务器这是真实请求。
<form action="your-bank.com/transfer" method="POST">
<input type="hidden" name="token" value="dad3weg34">
...
</form>
之所以被CSRF攻击,是因为恶意网站诱导你在其页面上发送了第三方cookie(此时的银行网站cookie为第三方cookie),它除了用于 CSRF 攻击,还可以用于用户追踪。
第三方是一种相对概念,规定假定你正在访问的站点为第一方站点,则浏览器为第二方,其他网站就是第三方站点,第三方站点诱导你发送第一方站点的cookie时,我们就说此时的cookie为第三方cookie。
比如,Facebook 在第三方网站插入一张看不见的图片。
<img src="facebook.com" style="visibility:hidden;">
浏览器加载上面代码时,就会向 Facebook 发出带有 Cookie 的请求,从而 Facebook 就会知道你是谁,访问了什么网站。
SameSite
cookies机制一直被认为是不安全的,随着技术的更新,界内一直在完善cookies的安全机制,SameSite
属性是谷歌浏览器为完善cookies安全机制出的特性之一。
Cookie 的SameSite
属性用来限制第三方 Cookie的行为。
它可以设置三个值。
- Strict
- Lax
- None
Strict
Strict
最为严格,完全禁止第三方 Cookie,当当前站点与请求目标站点是跨站
关系时,总是不会发送 Cookie。换言之,只有当前站点 与请求目标站点是同站
关系时,才会带上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
这个规则过于严格,可能造成非常不好的用户体验。
举例说明:
假定你当前所处站点地址为https://obmq.com/index.html
,该站需要通过XHR请求获取某天气站点的未来7天的天气信息https://weather-forecast.org/api/weather?future=7
,该接口要求必须携带cookieSet-Cookie: vip=true; Path=/; HttpOnly; SameSite=Strict;
,这种情况下,你无论如何都无法在https://obmq.com
站点下发送这个XHR请求时还能携带上这个cookie,换句话说,你发送的接口请求的cookie请求头一定不会有vip=true
,即使你现在已经是该天气网站的vip。
None
None
在Chrome 85 版本之前是SameSite
的默认设置值,即Set-Cookie: key=value; SameSite=None
等于Set-Cookie: key=value
。
在Chrome 85 版本之前,显示设置SameSite=None
不需要设置Secure
属性
在Chrome 85 版本以后,站点选择显式关闭SameSite
属性时,在将其值设为None
的同时。必须同时设置Secure
属性(表示Cookie 只能通过 HTTPS 协议发送),否则无效。
下面的设置有效。
Set-Cookie: widget_session=abc123; SameSite=None; Secure
下面的设置无效。
Set-Cookie: widget_session=abc123; SameSite=None
Lax
Chrome 在85版本后将Lax
设为SameSite
的默认值,即Set-Cookie: key=value; SameSite=Lax
等于Set-Cookie: key=value
Lax
规则比较宽松,大多数情况也不发送第三方 Cookie,但是导航到目标站点的 Get 请求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
导航到目标站点的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。
请求类型 | 示例 | SameSite=None;Secure | Lax |
---|---|---|---|
链接 | <a href="…"></a> | 发送 Cookie | 发送 Cookie |
预加载 | <link rel=“prerender” href="…"/> | 发送 Cookie | 发送 Cookie |
GET 表单 | <form method=“GET” action="…"> | 发送 Cookie | 发送 Cookie |
POST 表单 | <form method=“POST” action="…"> | 发送 Cookie | 不发送 |
iframe | <iframe class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="…"></iframe> | 发送 Cookie | 不发送 |
xhr/fetch | $.get("…") | 发送 Cookie | 不发送 |
Image | <img class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="…"> | 发送 Cookie | 不发送 |
Script | <script class="lazyload" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsQAAA7EAZUrDhsAAAANSURBVBhXYzh8+PB/AAffA0nNPuCLAAAAAElFTkSuQmCC" data-original="…"> | 发送Cookie | 不发送 |
设置了Strict
或Lax
以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite
属性。
Schemeful Same-Site
自Chrome 86版本开始,考虑到不安全的http://
协议仍然为网络攻击者提供了篡改cookie的机会,然后将这些cookie用于站点安全的https://
。谷歌浏览器修改了cookie的Same Site
的定义,将在相同域名的安全(https://)协议和不安全(http://)协议作为是否跨站
的判断因素之一。
如果您的站点全面升级到https协议,那么下面的内容不适合您;如果您的站点是https协议和http协议混存,那么您需要关注。
常见的 “cross-scheme” Cookies携带情况
超链接
当Schemeful Same-Site
禁止时,从http:// site.example链接到
https://site.example时,即使SameSite=Strict
依然会携带上cookie。
当Schemeful Same-Site
启用时,从http:// site.example链接到
https://site.example时,SameSite=Strict
的cookies会被锁定,其他cookies携带的表现如下图和下表:
HTTP → HTTPS | HTTPS → HTTP | |
SameSite=Strict
|
⛔ Blocked | ⛔ Blocked |
SameSite=Lax
|
✓ Allowed | ✓ Allowed |
SameSite=None;Secure
|
✓ Allowed | ⛔ Blocked |
加载子资源
加载子资源的方式包括images
, iframes
, 和XHR or Fetch
的网络请求。
加载子资源分为http加载https子资源
和https加载子http资源
,携带cookies的表现如下图和下表:
当Schemeful Same-Site
禁止时,加载子资源时,SameSite=Strict
或者SameSite=Lax
的cookie会被携带上。
当Schemeful Same-Site
启用时,加载子资源时,SameSite=Strict
或者SameSite=Lax
的cookies会被锁定,其他cookies携带的表现如下图和下表:
HTTP → HTTPS | HTTPS → HTTP | |
SameSite=Strict
|
⛔ Blocked | ⛔ Blocked |
SameSite=Lax
|
⛔ Blocked | ⛔ Blocked |
SameSite=None;Secure
|
✓ Allowed | ⛔ Blocked |
POST 表单
当Schemeful Same-Site
禁止时,发送POST表单时,SameSite=Strict
或者SameSite=Lax
的cookie会被携带上。
当Schemeful Same-Site
启用时,发送POST表单时,只有SameSite=None
的cookies会被携带,其他cookies携带的表现如下图和下表:
HTTP → HTTPS | HTTPS → HTTP | |
SameSite=Strict
|
⛔ Blocked | ⛔ Blocked |
SameSite=Lax
|
⛔ Blocked | ⛔ Blocked |
SameSite=None;Secure
|
✓ Allowed | ⛔ Blocked |
对WebSockets的影响?
如果WebSocket连接与页面的安全性相同,则仍将被视为同站。
https://
连接wss://
被视为同站;http://
连接ws://
被视为同站,否则视为跨站。详细如下:
Same-site:
wss://
从https://
连接ws://
从http://
连接
Cross-site:
wss://
从http://
连接ws://
从https://
连接
最佳实践
依然使用token机制防止CSRF攻击
设置了SameSite属性值为Strict
或Lax
以后,基本杜绝了 CSRF 攻击。但是SameSite是Cookies属性之一,所以存在以下诸多限制:
-
要求浏览器必须兼容Cookies的SameSite属性
-
要求客户端应用必须支持Cookies机制,比如APP和微信小程序并不支持Cookies
限制如上所列,但不限于所列。
因为SameSite属性存在以上限制,所以需要服务器端依然采用token机制来防止CSRF攻击。
显示指定SameSite属性值为Lax
或Strict
总应该设置一个显式的SameSite
属性,而不是依赖浏览器为您应用的默认设置。这使您对Cookie的使用意图更加明确,并提高了跨浏览器获得一致体验的机会。
不兼容的客户端的处理
对于SameSite
属性的兼容性在不同浏览器和相同浏览器的不同版本之间是不同的,可以参考chromium.org上的更新页面的已知的不兼容客户端以了解当前已知的问题,但是无法确定是否详尽无遗。 尽管这不是理想的选择,但可以在此过渡阶段中采用一些解决方法。
方式一:同时设置兼容SameSite属性的客户端和不兼容SameSite属性的客户端
// ~ 同时设置兼容SameSite属性的客户端和不兼容SameSite属性的客户端
// ~ 对于兼容SameSite属性的客户端,显示指定SameSite属性值以明确使用意图
Set-cookie: 3pcookie=value; SameSite=None; Secure
// ~ 对于不兼容SameSite属性的客户端,不设置SameSite属性
Set-cookie: 3pcookie-legacy=value; Secure
下面的示例显示了如何使用[Express框架]及其[cookie-parser]中间件在Node.js中执行此操作。
const express = require('express');
const cp = require('cookie-parser');
const app = express();
app.use(cp());
app.get('/set', (req, res) => {
// Set the new style cookie
res.cookie('3pcookie', 'value', { sameSite: 'none', secure: true });
// And set the same value in the legacy cookie
res.cookie('3pcookie-legacy', 'value', { secure: true });
res.end();
});
app.get('/', (req, res) => {
let cookieVal = null;
if (req.cookies['3pcookie']) {
// check the new style cookie first
cookieVal = req.cookies['3pcookie'];
} else if (req.cookies['3pcookie-legacy']) {
// otherwise fall back to the legacy cookie
cookieVal = req.cookies['3pcookie-legacy'];
}
res.end();
});
app.listen(process.env.PORT);
方式二:判断客户端的user-agent
在发送Set-Cookie
相应头时,您可以选择通过user-agent
字符串检测客户端。建议您找一个工具库来处理user-agent
,因为您很可能不想自己编写这些正则表达式。
这种方法的好处在于,它只需要在设置cookie时进行一次更改即可。 但是,此处必须指出是user-agent
嗅探本身是不可靠的,并且可能无法捕获所有受影响的用户。
无论选择哪种方式,建议确保有一种记录低版本客户端比例的方法。 一旦比例下降到网站可接受的阈值以下,请确保您有提醒或警报以删除此替代方法。
谷歌计划推出"Privacy Sandbox"
谷歌计划完全禁止第三方cookie,毕竟cookie真的不是很安全
文章同步发布在各大主流知识共享平台,所以设github
为统一的反馈区
疑问、讨论、问题反馈:github.com/weixsun/discussion
共同学习,写下你的评论
评论加载中...
作者其他优质文章