首先我们谈谈什么是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,很显然加载失败了,因为它不是一个有效的图片格式。但是,这个请求依然被发送出去了,此时你的微博是登录状态中,然后,你就真的follow了123, 你看,你被强奸了。 这就是简单的csrf攻击。 在实际的网站项目中,如: `http://www.abc.com/logout` 之类的链接都应该注意,注销类的、删除内容类的、转账类的都可能中埋伏,轻则让你感到诧异,重则数据丢失,财产损失,所以要重视任何一个对数据有操作行为的url。那么我们在CI里如何解决呢? 简单地: 第一步: 在 `application/config/config.php` 里配置以下字段: $config['csrf_protection'] = true; $config['csrf_token_name'] = 'csrf_token_name'; $config['csrf_cookie_name'] = 'csrf_cookie_name'; $config['csrf_expire'] = 7200; 第二步: 在form里使用`form_open()`,帮助生成一个token。 接下来我说一下csrf的工作原理: 简单地来说,当我们访问一个页面如: `http://www.abc.com/register` 时, CI会生成一个名为csrf_cookie_name的cookie,其值为hash,并发送到客户端。同时由于你在该页面里使用了 form_open(),会在form标签下生成一个``之类的隐藏字段,其值也为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里都加入这样一个传递数据: $.post(url, {'security->get_csrf_token_name(); ?>' : 'security->get_csrf_hash(); ?>'}, function(){}); (2)为ajax请求加入全局传递数据: $(function($) { // this script needs to be loaded on every page where an ajax POST may happen $.ajaxSetup({ data: { 'security->get_csrf_token_name(); ?>' : 'security->get_csrf_hash(); ?>' } }); // now write your ajax script }); (3)自己写一个helper方法,直接在view中使用,加入隐藏字段,如果你不喜欢使用form_open()的话: function csrf_hidden() { $ci = &get_instance(); $name = $ci->security->get_csrf_token_name(); $val = $ci->security->get_csrf_hash(); echo ""; }