- 原文连接:Cross-Site Request Forgery is dead!
- 原文做者:Scott
- 译文出自:掘金翻译计划
- 译者:XatMassacrE
- 校对者:newbieYoung,DeadLion
在接二连三的被跨站请求伪造折磨了这么多年后,咱们如今终于有了一个合理的解决方案。一个对网站拥有者没有技术负担、实施起来没有难度、部署又很是简单的方案,它就是 Same-Site Cookies。javascript
跨站请求伪造(又被称为 CSRF 或者 XSRF )彷佛一直都存在着。它源自一个网站必须向另外一个网站发出请求的简单功能。好比像在页面中嵌入下面的表单代码。html
<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 片断来实现提交。java
document.getElementById("stealMoney").submit();复制代码
这就是被称做 CSRF 的来历。我伪造了一个跨站到你的银行网站的请求。这个问题的关键不是我发送了请求,而是你的浏览器经过这个请求发送了你的 cookies。此时,你当前拥有的所有验证信息也会经过这个请求发送,这就意味着你登陆你的银行帐户而且捐助了我 £1,000 。谢谢啊!那么当你没有登陆的时候,这个请求对你就没有什么影响了,由于你不登陆是没法转帐的。不过对于银行来讲,他们如今采用的下面几种办法能够在必定程度上防护 CSRF 攻击。git
关于缓解 CSRF 这里就不详细展开讲了,由于网上关于这个话题已经有大量的信息了,可是我仍然会快速的过一遍顺便展现一下实现他们都须要哪些技术。github
当咱们收到一个请求时,关于这个请求的来源有两个地方的信息对咱们来讲是有用的。一个是 Origin header,另外一个是 Referer header。你能够检查他们中的一个或者两个的值来断定对于你的网站来讲他们是否是来自一个不一样的域。若是这个请求是跨域的,那么你把它丢掉就能够了。Origin 和 Referer header 会在浏览器端作一些保护措施来阻止被纂改,可是这并不老是有效的。web
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复制代码
一般状况下你能够经过两种方法来实现 Anti-CSRF tokens,可是它们的原理是同样的。当一个游客请求一个页面时,相似于上面提到的转帐页面,你能够在表单中嵌入一个随机的token。当真正的用户提交表单的时,你就会收到表单的随机 token,这样你就能够经过以前嵌入的那个随机 token 来校验了。在 CSRF 攻击场景中,攻击者永远都不可能拿到这个值甚至在攻击者能够请求到页面的状况也没法拿到,由于同源策略(SOP)会阻止攻击者从包含 token 的响应中读取内容。这个方法在实际运用中很不错,可是它须要网站追踪每个请求而且返回 Anti-CSRF tokens。还有一个相似的在表单中嵌入 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 header 并非 100% 有效的,大部分网站也会经过一些高级的 Anti-CSRF token 方式来防护。问题是,这两种方法都须要网站有一些必要的条件才能实施和维护。虽然这些条件并非世界上最复杂的技术,可是咱们仍然须要创建一个解决办法来让浏览器作一些咱们不想让它作的事情。既然这样的话,那么咱们为何不直接告诉浏览器不要作那些咱们不想让它们作的事情呢?如今,咱们能够了!浏览器
你或许已经在我最近的博客( Tough Cookies)上看到了一些关于 Same-Site Cookies 的内容,可是在这里我将会用一些例子来深刻的讲解。从本质上来说,Same-Site Cookies 能够彻底有效的阻止 CSRF 攻击,是的,CSRF 一点机会都没有。咱们在互联网上真正需求的本质就是赢得网络安全的战争,Same-Site Cookies 很是容易部署,是真的很是容易。找到你原来的 cookie :安全
Set-Cookie: sess=abc123; path=/复制代码
添加 SameSite 这个属性。cookie
Set-Cookie: sess=abc123; path=/; SameSite复制代码
你已经完成了。严格来说,就是这样!在 cookie 上启用这个属性将会告诉浏览器给予这个 cookie 确切的保护。你能够经过 Strict 和 Lax 这两种模式来启用这个保护,具体用哪一种模式取决于你想要的严格程度。若是在你的 cookie 设置中没有指定模式的话默认将会使用 Strict 模式,可是若是你想的话你能够明确的指定是 Strict 仍是 Lax。
SameSite=Strict
SameSite=Lax复制代码
很显然,将你的 SameSite 保护设置为 Strcit 模式是一个更好的选择,可是咱们之因此有两个选项的缘由是由于不是全部的网站都是同样的而且不是全部的网站都有一样的需求。当咱们在 Strict 模式下操做时,浏览器在任何跨域请求中都不会携带 cookie,因此说 CSRF 一点机会都没有。可是问题是,顶级导航(直接在地址栏改变 URL )的请求都不会携带 cookie。好比说有一个连接地址 facebook.com 而且 Facebook 的 SameSite cookies 的模式为 Strict,当你点击连接打开 Facebook 以后你会发现你没法登陆。不管你以前是否登陆,在新标签中打开,不管你怎么作,当你从那个连接过来时你都没法登陆到 Facebook。这就很烦人了,而且咱们的用户也不但愿咱们提供如此蛋疼的保护。这时候 Facebook 要作的就是向 Amazon 学习,使用两个 cookie。一种是用来验证用户信息和登陆操做的 '基础的' cookie,当你想进行一些相似于支付,改变帐户信息的敏感操做时就须要第二种 cookie 了,'真正的' cookie 就能够容许你进行一些重要的操做。在这个案例中第一种 cookie 就是一种 '方便的' 不会设置 SameSite 的 cookie,它真的不回容许你进行任何敏感性的操做,即便攻击者经过它来进行跨站请求,什么都不会发生。第二种 cookie 是一种设置了 SameSite 属性的 '敏感的' cookie,攻击者在跨站请求中不会获取它的权限。这对于用户和安全来讲就是一种理想的解决方案。然和这种方式的实施性并不强,由于咱们但愿 SameSite cookies 能够简单的部署,那么咱们就须要第二个选项了。
将 SameSite 保护设置为 Lax 模式将会解决上面提到的在 Strict 模式下的用户在已经登陆的前提下点击连接仍然没法在目标网站登陆的问题。在 Lax 模式下有一个例外,就是在顶级导航中使用一个安全的 HTTP 方法发送的请求能够携带 cookie。所谓 "安全的" 的 HTTP 方法在 Section 4.2.1 of RFC 7321 定义为 GET、HEAD、OPTIONS 和 TRACE,在这里咱们只关心 GET 方法,就是咱们连接到 facebook.com 的顶级导航就是一个 GET 方法。如今当用户点击一个设置了 SameSite 的连接以后,浏览器就会发送携带 cookie 和一些咱们但愿的用户信息的请求。同时,咱们也防范了基于 POST 方法的 CSRF 攻击。在 Lax 模式下,最开始提到的例子中的攻击手段也没法成功。
<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 的。那么攻击者固然会想到使用一种 '安全的' 方法来完成一样的请求。
<form action="https://your-bank.com/transfer" method="GET" 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 请求的地方不接受 GET 请求那么这种攻击方法就会失效,可是在 Lax 模式下还有一些须要注意的点。好比,若是一个攻击者触发一个顶级导航或者弹出一个新的窗口,那么他们就可让浏览器发送一个携带 cookies 的 GET 请求。这就是在 Lax 模式下须要取舍的地方,咱们在保证完整的用户体验的前提下不得不承担一些小的风险。
这篇博客的目标是经过 SameSite Cookies 来缓解 CSRF 攻击,可是,你可能已经猜到了,这种机制还有一些其余的用途。第一个就是跨站脚本包含(XSSI),它是指当浏览器向相似于脚本的资源文件发送请求的时候将会根据用户是否登陆而作出改变。在跨站请求的场景中,一个攻击者没法使用 SameSite Cookie 的一些验证信息来形成不一样的响应。这里还有一些有趣的定时攻击的详细信息。
还有一个有趣的用途(不是很详细)是用来对抗在面对浑水猛兽般的攻击手段下 (CRIME), BREACH), HEIST, TIME) 形成的会话 cookie 的泄露。这些确实是很高级的攻击手段,可是基础的场景是一个 MiTM (中间人攻击) 能够经过任何他们喜欢或监视的机制来强行让浏览器发送跨域请求。经过使用请求载荷的大小的变化,攻击者能够变动浏览器请求并观察每次变动以后的大小就能够猜出一位 session ID 的值。而使用 SameSite Cookies 的话,浏览器在发送这些请求的时候就不会携带 cookies,那么攻击者业就没法猜到他们的值了。
和不少新的浏览器安全特性同样,咱们老是但愿 Firefox 和 Chrome 可以引领这些新特性,可是此次状况不同了。Chrome 自从 v51 就开始支持 SameSite Cookie 了,这意味着 Opera,安卓浏览器和安卓上的 Chrome 都支持这一特性。你能够在 caniuse.com 上看到当前全部支持该属性的详细信息,Firefox 还有一个开放的 bug 须要添加支持。虽然目前来看支持并非很全面,可是咱们应该给咱们的 cookies 添加 SameSite 这个属性。支持这一特性的浏览器将会按照协议为咱们的 cookie 提供额外的保护,而不支持的浏览器会直接无视它。这不但对咱们没什么影响,还会提供一种不错的具备深度的防护手段。虽然离咱们彻底移除传统的反 CSRF 机制还有很长的一段时间,可是添加 SameSite 仍然能够为咱们提供一个足够健壮的保护。