原文地址:
Cross-Site Request Forgery is dead!
https://scotthelme.co.uk/csrf-is-dead/

对于WEB CSRF(跨站请求伪造)攻击,解决方案一直以来都比较复杂。传统的csrf token解决方法,要求网站管理者拥有专业的技术知识,现在我们找到了一个更好的解决方案————Same-Site Cookies,这种方案让网站管理者告别技术负担,并且不再需要复杂的操作,就能非常简单地完成csrf防护。

CSRF历史

众所周知,跨站请求伪造(也称为CSRF或XSRF)一直就存在,这就好比一个网站向另一个网站发起请求,假设我在一个页面中嵌入了以下表单:

<form action="https://your-bank.com/transfer" method="POST" id="stealMoney">
<input type="hidden" name="to" value="Scott Helme">
<input type="hidden" name="account" value="14278935">
<input type="hidden" name="amount" value="£1,000">

当浏览器加载此页面时,会渲染上面的表单,然后我们使用一个简单的JS:
document.getElementById("stealMoney").submit();

我在另一个网站,伪造了一个请求发送给银行网站。这里的真正问题不在于我发送请求,而是浏览器会将cookies带上一并发出去。这个伪造的请求将拥有当前用户的完整权限,这意味着如果用户登录了银行网站,用户将会向我捐赠1,000英镑。 谢谢! 如果用户没有登录,那么请求将是无害的,因为伪造的请求无法在没有登录的情况下转移资金。

针对这个情况,银行网站有几种方法可以减轻这些CSRF攻击。

CSRF防护措施

本文不会详细讲解防护csrf的原理,大家可以通过网络搜到很多这些方面的文章。为了更好的理解后面的内容,我会尽量快速简单的给大家过一遍。

1. 检查来源

当服务器收到请求时,可能会收到两个有用的字段信息,这两个信息可以告诉服务器请求来自哪里。 这两个字段分别是Origin和Referer。我们可以检查它们中的一个或两个,来确认请求是否来自同一个网站。 如果请求是跨站的,出于安全我们不处理。

Origin和Referer确实可以起到安全作用,防止请求伪造,但是这两个字段可能并不总是存在。

accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
accept-encoding: gzip, deflate, br
cache-control: max-age=0
content-length: 166
content-type: application/x-www-form-urlencoded
dnt: 1
origin: https://report-uri.io
referer: https://report-uri.io/login
upgrade-insecure-requests: 1
user-agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36

2. 防CSRF令牌机制

有两种不同的方法可以实现防CSRF令牌机制,但是原理是一样的。

当访客请求一个页面时,比如上面的示例中的转账页面,我们可以在表单中嵌入一个随机token(一般叫csrf token)。当真实用户提交此表单时,随机token也会跟着提交,服务器可以检查提交请求中的token是否和表单中的令牌匹配。在CSRF攻击场景中,攻击者永远不会获得token值,即使他们请求了转账页面,因为同源策略(SOP)将阻止攻击者读取包含该令牌的响应。这种方法比较行之有效,但要求服务器管理csrf token的生成和校验。

第二种类似的方法,是将token嵌入到表单中,并向浏览器写入相同值的cookie。当真实用户提交请求时,会将表单数据和cookie数据都提交上去,服务器进行匹配。当攻击者发送伪造的请求时,浏览器将不会设置CSRF cookie,攻击将失败。

<form action="https://report-uri.io/login/auth" method="POST">
<input type="hidden" name="csrf_token" value="d82c90fc4a14b01224gde6ddebc23bf0">
<input type="email" id="email" name="email">
<input type="password" id="password" name="password">
<button type="submit" class="btn btn-primary">Login</button>
</form>

问题

上面提到的两种方法,提供了CSRF强有力的防护。

不过检查Origin和Referer不是100%可靠,大多数站点会根据情况进行适应,我们需要对网站增加专门的csrf防护方案。虽然csrf防护,可能不是非常复杂的技术,但是我们也看到了,为了csrf攻击得到解决,我们围绕浏览器做了很多额外的操作,这些操作都与业务没有关系。

难道我们一定要做这些额外的事情么?现在我们有了新的答案!

Same-Site Cookies

想要深入了解cookie,可以参考我的这篇文章:
https://scotthelme.co.uk/tough-cookies/

我会提供几个例子以便大家深入理解Same-Site Cookies,基本上,它可以完全有效地阻止CSRF攻击。

哈,CSRF————死了!结束了!一路平安!

对于哪些希望得到安全保护的cookie,使用非常简单,这是目前的常见一个cookie:

Set-Cookie: sess=abc123; path=/

加上Same-Site之后的样子:

Set-Cookie: sess=abc123; path=/; SameSite

完成了。怎么样,很简单吧!

cookie上启用Same-Site属性时,浏览器将会自动对该cookie进行保护。保护一共有两种模式,分别是Strict和Lax,具体取决于我们网站的安全程度。默认情况下,取Strict,但如果需要,也可以明确设置Strict或者Lax。

SameSite=Strict
SameSite=Lax

1. Strict(严格模式)

将SameSite属性设置为严格模式显然是首选,但这种模式并不适合所有网站。

在严格模式下运行时,浏览器将不会发送任何跨站请求的cookie,所以可以使CSRF彻底失败。但是我们会遇到的最大问题是,点击其它网站跳转时,它也不会发送cookie。

如果我的网站有一个指向https://facebook.com的链接,并且Facebook将SameSite Cookie设置为严格模式,当用户点击链接打开Facebook时,用户将不会登录。无论用户是否已经登录,无论用户做了什么,用户都不会在从该链接访问时登录到Facebook。这可能会让用户不爽或者感到意外,但确实提供了令人难以置信的强大的保护。

刚才的例子确实有问题,需要修改Facebook策略,可以参考亚马逊的做法。亚马逊提供2个cookie,一种是基本的cookie,用于进行用户标识,并允许用户具有登录体验,但如果用户想做一些敏感的事情,比如进行购买或更改帐户时,就需要更严格的cookie。


基本的cookie做普通的事情,严格的cookie做重要的事情。

在这种情况下,基本的cookie不会将SameSite属性设置为Lax,方便用户使用。严格的cookie设置SameSite属性为Strict,攻击者不能滥用进行csrf攻击。这是用户体验和安全性的理想解决方案。

2. Lax(松散模式)

将SameSite设置为松散模式,可以解决在用户点击链接的严格模式下的问题,如果用户已经登录,则不会在目标站点上登出。

在Lax模式下,RFC 7321的4.2.1节中定义GET,HEAD,OPTIONS和TRACE这些方法会保留cookie,我们对GET方法感兴趣。这意味着当用户点击链接时,浏览器发出请求时,会带上标记为Lax的cookie,保证了预期的用户体验。

Lax,会对POST的CSRF攻击进行保护保护,就像最开始提到的那个例子,使用Lax可以避免csrf攻击。

<form action="https://your-bank.com/transfer" method="POST" id="stealMoney">
<input type="hidden" name="to" value="Scott Helme">
<input type="hidden" name="account" value="14278935">
<input type="hidden" name="amount" value="£1,000">

因为在松散模式下,POST方法依旧不被认为是安全的,所以浏览器不会在请求中附加cookie。

在web开发时,我们一定要注意,不要允许get请求代替post请求,因为这样会导致lax模式下的csrf攻击。此外,如果攻击者可以触发弹出新窗口,还可能导致浏览器发出带有附加cookie的GET请求。所以在Lax模式下,我们保持用户体验完整,但是可能会有一些风险。

附加用途

本篇文章的目的是使用SameSite Cookies进行CSRF防护,大家可能已经猜到了,这种机制还有其他用途。

第一个是跨站脚本包含攻击方法(XSSI),它是浏览器对脚本的请求,该脚本将根据用户是否被认证而改变。在跨站请求场景中,攻击者不能滥用SameSite Cookie以产生不同的响应。

另一个有趣的用法是防止在BEAST攻击(CLIME,BREACH,HEIST,TIME),这里不详细介绍。这类攻击,会泄露会话cookie的值。中间人攻击一般会通过跨站发起攻击请求,并通过有效载荷大小的变化,来猜测会话ID值。使用SameSite Cookie浏览器不会在此类请求中包含Cookie,因此攻击者无法猜测其值。

浏览器支持

随着浏览器安全功能的更新,期待Firefox或Chrome等浏览器陆续采用,目前Chrome 51版本之后已经全部支持SameSite属性。这意味着Opera,Android浏览器和Android平台的Chrome等,都已经支持。

我们可以在caniuse.com上看到列出当前支持的详细信息:
http://caniuse.com/#search=SameSite

Firefox bug关于SameSite支持的记载:
https://bugzilla.mozilla.org/show_bug.cgi?id=795346

目前SameSite Cookie还不是很普及,但还是建议网站支持这个属性。这样会让支持它的浏览器访问更安全,不支持的浏览器也不会造成任何影响。

forgery 伪造
toiling 辛苦,劳累
implementation 履行,完成
burden 负担
trivially 平凡的
mitigation 缓解,平缓,防护措施
origin 起源,来源
potentially 潜在的
tampering 鼓捣,篡改
anti- (前缀)反对,防止
embed 把……潜入
genuine 真的
scenario 剧本,剧情
robust 强壮的,强健的
complicate 使复杂化
neutralise 抵消,废弃
dead/finito/adios 死掉/终止/再见
lax 松的,不严格的