文章

CSRF防御之道

CSRF防御之道

CSRF防御之道

CSRF(Cross Site Request Forgery,跨站域请求伪造)是一种网络的攻击方式,该攻击可以在受害者不知情的情况下以受害者的名义伪造请求发送至受攻击站点,从而在未授权的情况下执行在权限保护下的操作,有很大的危害性

在业界目前防御防御CSRF攻击主要有三种策略:验证HTTP Referer; 在请求的地址中添加token验证;在http头中自定义属性并验证。

验证HTTP Referer字段

根据HTTP协议,在HTTP头中有一个字段叫Referer,它记录了该HTTP请求的来源地址。在通常情况下,访问一个安全受限页面的请求来自于同一个网站,比如需要访问 http://xxx.com/index.php,用户必须先登陆xxx.com,然后通过点击页面上的按钮来触发事件。这时请求的Referer值会在新的页面URL中,而黑客CSRF攻击一般都是在自己的网站上伪造请求到攻击网站,这个时候的请求的Referer是指向黑客自己的网站。因此,要防御CSRF攻击,只需要对于每一个请求验证其Referer值,如果是以xxx.com开头的域名,则说明该请求是来自网站自己的请求,是合法的。如果Referer是其他网站的话,则有可能是黑客的CSRF攻击,拒绝该请求。 这种方法的显而易见的好处就是简单易行,网站的普通开发人员不需要操心CSRF的漏洞,只需要在最后给所有安全敏感的请求统一增加一个拦截器来检查Referer的值就可以。特别是对于当前现有的系统,不需要改变当前系统的任何已有代码和逻辑,没有风险,非常便捷。 然而,这种方法并非万无一失。Referer的值是由浏览器提供的,从理论上来讲,这样并不安全。事实上,对于某些浏览器,比如 IE6 或 FF2,目前已经有一些方法可以篡改Referer值。 即便是使用最新的浏览器,黑客无法篡改Referer值,这种方法仍然有问题。因为Referer值会记录下用户的访问来源,有些用户认为这样会侵犯到他们自己的隐私权,特别是有些组织担心Referer值会把组织内网中的某些信息泄露到外网中。因此,用户自己可以设置浏览器使其在发送请求时不再提供Referer。当他们正常访问银行网站时,网站会因为请求没有 Referer 值而认为是CSRF攻击,拒绝合法用户的访问。

在请求中添加token并验证

CSRF攻击之所以能够成功,是因为黑客可以完全伪造用户的请求,该请求中所有的用户验证信息都是存在于cookie中,因此黑客可以在不知道这些验证信息的情况下直接利用用户自己的cookie来通过安全验证。要抵御CSRF,关键在于在请求中放入黑客所不能伪造的信息,并且该信息不存在于cookie之中。可以在HTTP请求中以参数的形式加入一个随机产生的token,并在服务器端建立一个拦截器来验证这个token,如果请求中没有token或者token内容不正确,则认为可能是CSRF攻击而拒绝该请求。 这种方法要比检查Referer要安全一些,token可以在用户登陆后产生并放于 session之中,然后在每次请求时把token从session中拿出,与请求中的token进行比对,但这种方法的难点在于如何把token以参数的形式加入请求。对于GET请求,token将附在请求地址之后,这样URL就变成http://url?csrftoken=tokenvalue。而对于POST请求来说,要在form的最后加上 <input type=”hidden” name=”csrftoken” value=”tokenvalue”/>,这样就把 token 以参数的形式加入请求了。但是,在一个网站中,可以接受请求的地方非常多,要对于每一个请求都加上token是很麻烦的,并且很容易漏掉,通常使用的方法就是在每次页面加载时,使用javascript遍历整个dom树,对于dom中所有的a和form 标签后加入token。这样可以解决大部分的请求,但是对于在页面加载之后动态生成的 html代码,这种方法就没有作用,还需要程序员在编码时手动添加token。 该方法还有一个缺点是难以保证token本身的安全。特别是在一些论坛之类支持用户自己发表内容的网站,黑客可以在上面发布自己个人网站的地址。由于系统也会在这个地址后面加上token,黑客可以在自己的网站上得到这个token,并马上就可以发动 CSRF攻击。为了避免这一点,系统可以在添加token的时候增加一个判断,如果这个链接是链到自己本站的,就在后面添加token,如果是通向外网则不加。不过,即使这个csrftoken不以参数的形式附加在请求之中,黑客的网站也同样可以通过Referer来得到这个token值以发动CSRF攻击。这也是一些用户喜欢手动关闭浏览器Referer 功能的原因。

在HTTP头中自定义属性并验证

这种方法也是使用token并进行验证,和上一种方法不同的是,这里并不是把token 以参数的形式置于HTTP请求之中,而是把它放到HTTP头中自定义的属性里。通过 XMLHttpRequest这个类,可以一次性给所有该类请求加上csrftoken这个HTTP头属性,并把token值放入其中。这样解决了上种方法在请求中加入token的不便,同时,通过XMLHttpRequest请求的地址不会被记录到浏览器的地址栏,也不用担心token 会透过Referer泄露到其他网站中去。 然而这种方法的局限性非常大。XMLHttpRequest请求通常用于Ajax方法中对于页面局部的异步刷新,并非所有的请求都适合用这个类来发起,而且通过该类请求得到的页面不能被浏览器所记录下,从而进行前进,后退,刷新,收藏等操作,给用户带来不便。另外,对于没有进行CSRF防护的遗留系统来说,要采用这种方法来进行防护,要把所有请求都改为XMLHttpRequest请求,这样几乎是要重写整个网站,这代价无疑是不能接受的。

本文由作者按照 CC BY 4.0 进行授权