CI的CSRF防范原理及注意事项

首先我们谈谈什么是CSRF,它就是Cross-Site Request Forgery跨站请求伪造的简称。很多开发者甚至不够重视这个问题,认为这不是安全漏洞,而不过是恶意访问而已,它的攻击原理我在这里简单地描述一下:有一天你打开你简单优雅逼格十足的谷歌浏览器,首先打开了一个tab页,登录并访问了你的微博首页。我们这里假设weibo.cn有这样一个网址: http://www.weibo.cn?follow_uid=123 ,意思是关注id为123的一个用户。这是一个正常的地址,访问也没有问题。紧接着你的QQ群里发来了一个让你感到好奇的链接,http://www.comeonbaby.com, 你禁不住诱惑打开了这个链接,并在浏览器里的另一个tab页里显示出来。紧接着,你打开你的微博tab页,发现无故关注了一个新的用户。咦,这是为何?原因很简单,很可能在你打开的http://www.comeonbaby.com链 接里存在着这样一个html元素: <img src="http://www.weibo.cn?follow_uid=123" alt="" />, 浏览器试图加载这个img,很显然加载失败了,因为它不是一个有效的图片格式。但是,这个请求依然被发送出去了,此时你的微博是登录状态中,然后,你就真 的follow了123, 你看,你被强奸了。这就是简单的csrf攻击。在实际的网站项目中,如: http://www.abc.com/logout之类的链接都应该注意,注销类的、删除内容类的、转账类的都可能中埋伏,轻则让你感到诧异,重则数据丢失,财产损失,所以要重视任何一个对数据有操作行为的url。那么我们在CI里如何解决呢?简单地:第一步: 在application/config/config.php里配置以下字段:

1$config['csrf_protection'] = true;
2$config['csrf_token_name'] = 'csrf_token_name';
3$config['csrf_cookie_name'] = 'csrf_cookie_name';
4$config['csrf_expire'] = 7200;

第二步: 在form里使用form_open(),帮助生成一个token。接下来我说一下csrf的工作原理:简单地来说,当我们访问一个页面如: http://www.abc.com/register时, CI会生成一个名为csrf_cookie_name的cookie,其值为hash,并发送到客户端。同时由于你在该页面里使用了 form_open(),会在form标签下生成一个<input type="hidden" name="csrf_token_name" value="12uffu2910"/>之类的隐藏字段,其值也为hash。紧接着用户点击了注册按钮,浏览器将这些数据包括csrf_token_name发送到(post到)服务器,同时也会将名为 csrf_cookie_nam的cookie发送回去。服务器会比较csrf_token_name的值(也就是hash) 与 csrf_cookie_name 的cookie值(同样也是hash)是否相同, 如果相同则通过,如果不同则说明是csrf攻击。接下来我们分析一下CI的源代码:CI在Codeigniter.php里会先加载Security类,接着加载Input类,这两个类在每次访问时都会自动加载的。先加载 Security类,该类的初始方法首先设置一个hash, 这个hash如果为空,则会在cookie里检查是否存在,如果存在则设为hash;否则会计算出一个新的hash。  接下来开始初始化Input类,导致调用$this->security->csrf_verify()方法。该方法首先判断该请求是否为 post请求,如果不是,则会设置一个名为csrf_cookie_name的cookie,其值为hash,如果是post请求,则会用post过来的 csrf_token值与csrf_cookie值比较,比较失败则输出错误;成功则会删除csrf_tookie的值,再次设置csrf_hash的值 (同上,先检查cookie,此时为空,则会新计算一个hash),紧接着又重新赋予了新的csrf_cookie值。在实际操作过程中, 如有一个register的视图,其页面必然后 form_open()的调用,该方法会产生一个 csrf_token的 hash值, 当post到一个 action时, 该action自然就会执行检查。由上可以知道:(1)如果开启了csrf保护,每次调用都会生成一个叫csrf_cookie_name的cookie, 并将值设为hash;(2)直到遇到一次post请求时才会将以前的cookie删除,重新生成一个hash, 如此反复。但是,…………细心的读者可能发现了, 我上文中举的例子是get请求,而CI的csrf只是设计了post请求的防范策略,那么请你想想,你在你的项目中是否存在着 get请求的 资源操作url地址呢?你是否对这样的url地址进行过csrf防范?我们的建议:(1)重要的资源操作,都尽量采取post请求,防止csrf攻击;(2)如果你执意使用get请求,也不是没有办法,原理跟上面也是类似的,比如上文提到的关注账号的操作,你可以设计这样一个地址: http://www.weibo.cn?follow_uid=123&token=73ksdkfu102 token是什么?是你随机产生的一个字符串,等用户发送回去后你依然做验证,如果验证通过,则执行后续的关注操作,如果没通过,我们就认为该操作是不合法的。 那个诱惑你的攻击者不可能知道每个人的token, 即使你点击了那个链接,依然不会被认为是有效的访问地址。一点建议:由于CI开启csrf保护是全局性的,这样就会导致你的任何post请求都需要加入csrf_token_name的数据字段,的确非常繁琐,有些人索性就关闭了。在这里给出三个解决方法:(1)每个form里都加入这样一个传递数据:

1$.post(url, {'<?php echo $this->security->get_csrf_token_name(); ?>' '<?php echo $this->security->get_csrf_hash(); ?>'}, function(){});

(2)为ajax请求加入全局传递数据:

01//
02$(function($) {
03    // this script needs to be loaded on every page where an ajax POST may happen
04    $.ajaxSetup({
05        data: {
06            '<?php echo $this->security->get_csrf_token_name(); ?>' '<?php echo $this->security->get_csrf_hash(); ?>'
07        }
08    });
09  // now write your ajax script
10});

(3)自己写一个helper方法,直接在view中使用,加入隐藏字段,如果你不喜欢使用form_open()的话:

1function csrf_hidden(){
2 $ci = &get_instance();
3 $name $ci->security->get_csrf_token_name();
4 $val $ci->security->get_csrf_hash();
5 echo "<input type=\"hidden\" name=\"$name\" value=\"$val\" />";
6}


from:http://www.ifixedbug.com/posts/codeigniter-csrf-story