CSRF的全称是(Cross Site Request Forgery),可译为跨域请求伪造,是一种利用用户带登录 态的cookie迚行安全操作的攻击方式。CSRF实际上并不难防,但常常被系统开发者忽略,从而埋下巨 大的安全隐患。
1.CSRF的攻击过程
为了试图说明CSRF的攻击过程,现在思考下面这个场景。
假如有一个博客网站,为了激励用户写出高质量的博文,设定了一个文章被点赞就能奖励现金的 机制,于是有了一个可用于点赞的API,只需传入文章id即可:
http://com.example.com/article/like?id=xxx
在安全策略上,限定必须是本站有效登录用户才可以点赞,且每个用户对每篇文章仅可点赞一 次,防止无限刷赞的情况发生。
这套机制推行起来似乎没什么问题,直我们发现有个用户的文章总是有非常多的点赞数,哪怕只是发表了一条个人状态也有非常多的点赞数,而这些点赞记录也确实都是本站的真实用户发起的。察觉到异常之后,开始对这个用户的所有行为进行排查,发现该用户几乎每篇文章都带有一张很特别的 图片,这些图片的URL无一例外地指向了对应文章的点赞API。由于图片是由浏览器自动加载的,所以每个查看过该文章的人都会不知不觉为其点赞。很显然,该用户利用了系统的CSRF漏洞实施刷赞, 这是网站开发人员始料未及的。
有人可能认为这仅仅是因为点赞API设计不理想导致的,应当使用POST请求,这样就能避免上面的场景。然而,当使用POST请求时,确实避免了如img、script、iframe等标签自动发起GET请求的问题,但这并不能杜绝CSRF攻击的发生。一些恶意网站会通过表单的形式构造攻击请求:
<form action="http://com.example.com/xxx/transfer" method="post">
<input type="hidden" name="money" value="10000" />
<input type="hidden" name="to" value="hacker" />
<input type="submit" value="点击我查看美女图片" />
</form>
假如登录过某银行站点而没有注销,其间被诱导访问了带有类似攻击的页面,那么在该页面一旦单击按钮,很可能会导致在该银行的账户资金被直接转走。甚至根本不需要单击按钮,而是直接用 JavaScript代码自动化该过程。
CSRF利用了系统对登录期用户的信任,使得用户执行了某些并非意愿的操作从而造成损失。如
何真正地防范CSRF攻击,对每个有安全需求的系统而言都尤为重要。
2.CSRF的防御手段
一些工具可以检测系统是否存在 CSRF 漏洞,例如,CSRFTester,有兴趣的读者可以自行了解。
在任何情况下,都应当尽可能地避免以GET方式提供涉及数据修改的API。在此基础上,防御 CSRF攻击的方式主要有以下两种。
(1)HTTPReferer HTTP Referer
是由浏览器添加的一个请求头字段,用于标识请求来源,通常用在一些统计相关的 场景,浏览器端无法轻易篡改该值。
回到前面构造POST请求实行CSRF攻击的场景,其必要条件就是诱使用户跳转到第三方页面,在第三方页面构造发起的POST请求中,HTTP Referer字段不是银行的URL(少部分老版本的IE浏览器可以调用API进行伪造,但最后的执行逻辑是放在用户浏览器上的,只要用户的浏览器版本较新,便可以避免这个问题),当校验到请求来自其他站点时,可以认为是CSRF攻击,从而拒绝该服务。
当然,这种方式简单便捷,但并非完全可靠。除前面提到的部分浏览器可以篡改 HTTP Referer 外,如果用户在浏览器中设置了不被跟踪,那么HTTP Referer字段就不会自动添加,当合法用户访问时,系统会认为是CSRF攻击,从而拒绝访问。
(2)CsrfToken认证
CSRF是利用用户的登录态进行攻击的,而用户的登录态记录在cookie中。其实攻击者并不知道用户的cookie存放了哪些数据,于是想方设法让用户自身发起请求,这样浏览器便会自行将cookie传送到服务器完成身份校验。
CsrfToken的防范思路是,添加一些并不存放于 cookie 的验证值,并在每个请求中都进行校验, 便可以阻止CSRF攻击。 具体做法是在用户登录时,由系统发放一个CsrfToken值,用户携带该CsrfToken值与用户名、密码等参数完成登录。系统记录该会话的CsrfToken 值,之后在用户的任何请求中,都必须带上该 CsrfToken值,并由系统进行校验。
这种方法需要与前端配合,包括存储CsrfToken值,以及在任何请求中(包括表单和Ajax)携带 CsrfToken值。安全性相较于HTTP Referer提高很多,但也存在一定的弊端。例如,在现有的系统中进行改造时,前端的工作量会非常大,几乎要对所有请求进行处理。如果都是XMLHttpRequest,则可以统一添加CsrfToken值;但如果存在大量的表单和a标签,就会变得非常烦琐。因此建议在系统开发之初考虑如何防御CSRF攻击。
3.使用Spring Security防御CSRF攻击
CSRF攻击完全是基于浏览器进行的,如果我们的系统前端并非在浏览器中运作,就应当关闭CSRF。Spring Security通过注册一个CsrfFilter来专门处理CSRF攻击。
在Spring Security中,CsrfToken是一个用于描述Token值,以及验证时应当获取哪个请求参数或请求头字段的接口。
public interface CsrfToken extends Serializable {
String getHeaderName();
String getParameterName();
String getToken();
}
CsrfTokenRepository则定义了如何生成、保存以及加载CsrfToken。
public interface CsrfTokenRepository {
CsrfToken generateToken(HttpServletReq