如何防御 CSRF 攻击

support 2018-08-13 1850浏览

CSRF是Web应用中相对复杂、且防御代价较大的一种攻击方式。

相关资料:

站外攻击

比如在A网站中,将100元支付给张三的请求是http://mysite.com/pay?to=zhangsan&ammount=100。攻击者在第三方网站创建一个类似的链接,诱使A网站的用户点击这个链接,如果A网站的用户刚好已经登录A网站,就会触发转账请求。更糟糕的是,你可能不需要点击任何链接,就已经被攻击了。比如将这个地址作为图片的src,如<img src='http://mysite.com/pay?to=zhangsan&ammount=100'/>。这就是所谓的隐式攻击,危害更大。

这时候很容易想到的防范措施是,不要接受任何以GET方式发起的付款请求。确实,这是一个基本的安全准则,GET方式只能用于获取数据,不能用于修改数据,修改数据要使用POST、PUT、DELETE等方式。但这并不能彻底防范CSRF攻击,攻击者可以创建一个iframe,在iframe里面创建一个method为POST的form,达到隐式攻击的目的。但这很明显的提高了攻击门槛,因为很多网站会允许用户发布链接(例如通过评论功能),却不会允许用户使用js创建一个iframe。

由于同源策略,使用xmlhttp(也就是ajax)无法发起站外攻击。除了链接和form表单,能够向不同域名发起请求的有<img> <script> <frame> <iframe>

需要注意避免攻击者使用<script>CORSJSONP获取到敏感数据,如下面介绍的Token值。

站内攻击

站外攻击需要用户已经登录了被攻击的网站,这需要一些巧合。而站内攻击则更容易实现这点。站内攻击需要结合XSS漏洞,或者能够发布链接甚至js编写权限。

防御方式

正确使用GET和POST方式

GET方式用于获取数据,POST方式用于修改数据。使用POST修改数据,可以提高攻击的门槛,甚至避免一些攻击。

检查Referer

对于站外攻击,这是代价最小的防御方式。即这个请求必须是同源(如上例中的mysite.com域名)发出来的请求,否则不予执行。

缺点在于有些用户会在禁止浏览器在发送请求时包含Referer,认为这样会暴露隐私,从而使得正常的请求也会被认为是CSRF攻击。另外IE6和FireFox2可以修改Referer,当然这不是个问题,现在几乎不会有人用这些已经淘汰的浏览器。

站内攻击由于是同源,无法使用检查的Referer的方式防范。

验证Token

在服务端生成一个无法预测的随机字符串(如UUID),将这个字符串保存在Session或Cookie里。每个修改数据的请求(非GET请求,如POST,PUT,DELETE,PATCH),必须在Header或Parameter中附带这个Token值,否则不予执行。GET请求不能附带Token,因为Referer信息会泄露GET请求的Token。异源域名虽然可以通过 img script iframe 等标签提交请求,但却无法读取到响应的内容,无法获取到Token值,所以无法伪造成合法请求。

这个Token值保存在Cookie里相对没那么安全,因为Header(比如指定Cookie)能够被其它域的请求修改(使用flash10.2和307重定向);并且在cookie被泄露后,无法进行失效处理。

但对于绝大部分应用来说,放在Cookie里已经足够好了,新版本的flash也应该修复了相应漏洞,并且flash将于2020年停止更新,正逐步退出历史舞台。在微软ASP.NET Web文档里的做法就是将Token放到Cookie里。放在Session里则会存在过期的问题,对软件和开发使用带来更大影响,代价过大。

Token存储方式的三种方式:

站内攻击防御的三个级别:

使用验证Token的方式改变了通常的编程习惯,每个涉及修改数据的操作都需要加上一个Token值。对于一个已完成的系统,修改的工作量是巨大的。如果使用Session存储Token还存在Session过期的问题,对使用带来一定困扰。防御CSRF导致软件的开发和使用都付出极大的代价。

对于已开发好的系统,站外攻击使用检查Referer,站内链接攻击禁止GET方式修改数据并杜绝XSS漏洞,是防御CSRF攻击代价最小的方式。

新开发的系统还是使用验证Token的方式较为妥当,使用Cookie存储Token,取得足够的安全性的同时开发代价也相对较低。

防御实现

使用Filter过滤所有非GET请求,如在Header或Parameter里不存在Token,则不予执行。并将Token保存在Cookie和Request中,便于页面调用。

表单提交代码:

<form action="..." method="post">
    ...
    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}">
    ...
</form>

AJAX提交代码:

// AJAX 请求全部在 Header 中加上 CSRF TOKEN。需要js-cookie组件用于获取Cookie。
$(function () {
    $(document).ajaxSend(function (e, xhr, options) {
        xhr.setRequestHeader("X-XSRF-TOKEN", Cookies.get("XSRF-TOKEN"));
    });
});
评论 2018-08-16 修改 by support

我来回答

请先登录再回答问题
点击查看大图插件