用户
 找回密码
 入住 CI 中国社区
搜索
查看: 15727|回复: 8
收起左侧

[安全] CI的CSRF防范原理及注意事项

  [复制链接]
发表于 2014-12-27 01:22:51 | 显示全部楼层 |阅读模式
本帖最后由 yuzhigang5460 于 2015-3-24 21:32 编辑

首先我们谈谈什么是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里配置以下字段:
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标签下生成一个<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里都加入这样一个传递数据:
  
PHP复制代码
 $.post(url, {'<?php echo $this->security->get_csrf_token_name(); ?>' : '<?php echo $this->security->get_csrf_hash(); ?>'}, function(){});
复制代码

(2)为ajax请求加入全局传递数据:
PHP复制代码
//
$(function($) {
    // this script needs to be loaded on every page where an ajax POST may happen
    $.ajaxSetup({
        data: {
            '<?php echo $this->security->get_csrf_token_name(); ?>' : '<?php echo $this->security->get_csrf_hash(); ?>'
        }
    });
  // now write your ajax script
});
复制代码

(3)自己写一个helper方法,直接在view中使用,加入隐藏字段,如果你不喜欢使用form_open()的话:
PHP复制代码
    function csrf_hidden(){
     $ci = &get_instance();
     $name = $ci->security->get_csrf_token_name();
     $val = $ci->security->get_csrf_hash();
     echo "<input type=\"hidden\" name=\"$name\" value=\"$val\" />";
    }
复制代码


上文可能有写的不对的地方,还请指正。http://www.ifixedbug.com/posts/codeigniter-csrf-story



评分

参与人数 2威望 +10 收起 理由
Hex + 5 赞一个!
Closer + 5 赞一个!

查看全部评分

发表于 2014-12-27 10:19:59 | 显示全部楼层
写得不错,得顶.
发表于 2014-12-27 12:42:12 | 显示全部楼层
支持 csrf中的非法http请求携带了普通用户的cookie信息,因此是有效的,执行了普通用户不想进行的动作
发表于 2014-12-29 08:04:34 | 显示全部楼层
{:soso_e179:}赞一个
发表于 2015-1-15 08:41:18 | 显示全部楼层
学习了,谢谢分享
发表于 2015-2-10 00:59:28 | 显示全部楼层
这篇文章这么点儿回复不科学啊
发表于 2015-3-24 12:15:57 | 显示全部楼层
{:soso_e179:}学习了。好文章。
发表于 2016-9-19 15:44:37 | 显示全部楼层
写得很不错
发表于 2016-11-21 11:23:53 | 显示全部楼层
请问楼主,关闭全局的csrf验证($config['csrf_protection'] = FALSE;)后,,在表单中手动加入{ '<?php echo $this->security->get_csrf_token_name(); ?>' : '<?php echo $this->security->get_csrf_hash(); ?>' }后提交,CI会执行csrf验证吗?

本版积分规则