为了账号安全,请及时绑定邮箱和手机立即绑定

Cookies的SameSite属性

标签:
Java

自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 不发送

设置了StrictLax以后,基本就杜绝了 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携带的表现如下图和下表:

Cross-scheme navigation from HTTP to HTTPS.

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携带的表现如下图和下表:

An HTTP page including a cross-scheme subresource via HTTPS.

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携带的表现如下图和下表:

Cross-scheme form submission from HTTP to HTTPS.

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属性值为StrictLax以后,基本杜绝了 CSRF 攻击。但是SameSite是Cookies属性之一,所以存在以下诸多限制:

  • 要求浏览器必须兼容Cookies的SameSite属性

  • 要求客户端应用必须支持Cookies机制,比如APP和微信小程序并不支持Cookies

限制如上所列,但不限于所列。

因为SameSite属性存在以上限制,所以需要服务器端依然采用token机制来防止CSRF攻击。

显示指定SameSite属性值为LaxStrict

总应该设置一个显式的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


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
JAVA开发工程师
手记
粉丝
0
获赞与收藏
0

关注作者,订阅最新文章

阅读免费教程

  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消