|
本帖最后由 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
|
评分
-
查看全部评分
|