前言
最近有部分开发者同事的 Chrome 被自动升级到 80+的版本,然后发现 网页登录后的请求没带上 cookies,导致用户验证失败。
主要发生在:前端页面 和 后台服务 不在一台服务器上,ip或者域名不同,即跨站请求时出现的。
这是因为谷歌从2月17日开始对 Chrome80+ 开启了 SameSite="Lax"
(限制跨站访问 Cookie)。
根据在线流量监控器StatCounter的数据,Chrome是最受欢迎的网络浏览器,这一变化将在2020年影响全球64%的互联网用户。请继续阅读以了解如何避免这种变化影响用户!
在介绍 SameSite 之前,先认识下 跨站 cookies,以及利用跨站 cookies 实现的 CSRF跨站攻击 和 用户追踪。
CSRF 跨站攻击
用一个示例来介绍:
受害者 Bob 在银行有一笔存款,通过对银行的网站发送请求 http://bank.example/withdraw?account=bob&amount=1000000&for=bob2
可以使 Bob 把 1000000 的存款转到 bob2 的账号下。通常情况下,该请求发送到网站后,服务器会先验证该请求是否来自一个合法的 session,并且该 session 的用户 Bob 已经成功登陆。
Set-Cookie:id=a3fWa;
黑客 Mallory 自己在该银行也有账户,他知道上文中的 URL 可以把钱进行转帐操作。Mallory 可以自己发送一个请求给银行:http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory
。但是这个请求来自 Mallory 而非 Bob,他未登录,不能通过安全认证,因此该请求不会起作用。
这时,Mallory 想到使用 CSRF 的攻击方式,他先自己做一个网站,在网站中放入如下代码: <form src="http://bank.example/withdraw?account=bob&amount=1000000&for=Mallory">
,并且通过广告等诱使 Bob 来访问他的网站。
当 Bob 访问该网站时,上述 url 就会从 Bob 的浏览器发向银行,而这个请求会附带 Bob 浏览器中的 cookie 一起发向银行服务器。这时,悲剧发生了,这个 url 请求就会得到响应,钱将从 Bob 的账号转移到 Mallory 的账号,而 Bob 当时毫不知情。等以后 Bob 发现账户钱少了,即使他去银行查询日志,他也只能发现确实有一个来自于他本人的合法请求转移了资金,没有任何被攻击的痕迹。而 Mallory 则可以拿到钱后逍遥法外。
用户追踪
比如天猫就是通过淘宝的 JSONP 接口来判断用户是否登录的
SameSite 属性
Cookie 的 SameSite 属性用来限制第三方 Cookie,从而减少安全风险。
它可以设置三个值。
- Strict
- Lax
- None
Strict
Strict 最为严格,完全禁止第三方 Cookie,跨站点时,任何情况下都不会发送 Cookie。换言之,只有当前网页的 URL 与请求目标一致,才会带上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Strict;
这个规则过于严格,可能造成非常不好的用户体验。比如,当前网页有一个 GitHub 链接,用户点击跳转就不会带有 GitHub 的 Cookie,跳转过去总是未登陆状态。
Lax
Lax 规则稍稍放宽,大多数情况也是不发送第三方 Cookie,但是导航到目标网址的 Get 请求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
导航到目标网址的 GET 请求,只包括三种情况:链接,预加载请求,GET 表单。详见下表。
设置了 Strict
或 Lax
以后,基本就杜绝了 CSRF 攻击。当然,前提是用户浏览器支持 SameSite 属性。
None
Chrome 已从2月17日将部分人群的 Lax 变为默认设置(以前的默认值是 None
)。
这时,网站可以选择显式关闭 SameSite
属性,即将其设为 None
。不过,前提是必须同时设置 Secure
属性(即表示 Cookie 只能通过 HTTPS 协议发送),否则无效。
下面的设置无效
Set-Cookie: widget_session=abc123; SameSite=None
下面的设置有效
Set-Cookie: widget_session=abc123; SameSite=None; Secure
解决办法
开发环境
如果是开发者,使用的 localhost,可以浏览器进入 chrome://flags/#same-site-by-default-cookies
,将 SameSite by default cookies
设为 disabled
生产环境
如果是正式发布的站点,有2种方式:
1、建议网站升级到 https,然后 cookies 中增加 SameSite=None; Secure
2个标识
如果不是 http,而又只设置了 SameSite=None
; 是无效的。None
必须和 Secure
一起使用。
其实谷歌在建议大家都升级到 https。
如下图,没有设置 Secure
的 cookies,即使加了 SameSite=None
也有警告
2、使用反向代理,将不同站点代理成同一域名。
不存在跨站,就没有这个问题了
ASP.NET+IIS 如何设置 SameSite
1、.NET 4.7.2 及以后的版本 为自定义 cookies 设置 SameSite
var c = new HttpCookie("test");
c.SameSite = SameSiteMode.None;
c.Secure = true;
2、Form验证
编辑 web.config
在 <system.web>
之间加入
<authentication mode="Forms">
<forms ..... cookieSameSite="Lax" />
</authentication>
3、ASP.NET 会话 cookie
编辑 Global.asax
void Session_Start(Object sender, EventArgs e)
{
Response.Cookies["ASP.NET_SessionId"].SameSite = SameSiteMode.Lax;
//while we're at it lets also make it secure
if (Request.IsSecureConnection)
Response.Cookies["ASP.NET_SessionId"].Secure = true;
}
4、.NET 4.8 中 cookie 的 “SameSite” 默认值是 Lax
可以修改 web.config
在 <system.web>
之间加入
<sessionState cookieSameSite="None" />
综合一下,可以在 web.config 的 <system.web> 中加上
<httpCookies sameSite="None" requireSSL="true" />
<sessionState cookieSameSite="None" />
<authentication mode="Forms">
<forms cookieSameSite="None" />
</authentication>
微软文档说 <httpCookies>
加上 requireSSL="true"
就能在 cookies 中带上 Secure
标识了,但是实际上没效果,不知道是不是 bug。
最终在 Stack Overflow 上找到一个方法
<system.webServer>
...
<rewrite>
<outboundRules>
<!-- 增加 "SameSite=None" 到所有 cookies 中-->
<rule name="Add SameSite" preCondition="No SameSite">
<match serverVariable="RESPONSE_Set_Cookie" pattern=".*" negate="false" />
<action type="Rewrite" value="{R:0}; SameSite=None" />
</rule>
<!-- 当 https 访问,增加 "Secure" 到所有 cookies 中 -->
<rule name="Add Secure" preCondition="No Secure">
<match serverVariable="RESPONSE_Set_Cookie" pattern=".*" negate="false" />
<action type="Rewrite" value="{R:0}; Secure" />
</rule>
<preConditions>
<preCondition name="No SameSite">
<add input="{RESPONSE_Set_Cookie}" pattern="." />
<add input="{RESPONSE_Set_Cookie}" pattern="; SameSite=" negate="true" />
</preCondition>
<preCondition name="No Secure">
<add input="{RESPONSE_Set_Cookie}" pattern="." />
<add input="{RESPONSE_Set_Cookie}" pattern="; Secure" negate="true" />
<add input="{HTTPS}" pattern="on" ignoreCase="true" />
</preCondition>
</preConditions>
</outboundRules>
</rewrite>
</system.webServer>
上面的 rewrite 需要 IIS 安装 URL Rewrite 模块
rewrite 之后,就有 Secure
了