4.3 CSRF漏洞
CSRF全称为Cross-site request forgery,跨站请求伪造。说白一点就是可以劫持其他用户去进行一些请求,而这个CSRF的危害性就看当前这个请求是进行什么操作了。
而CSRF是怎么一个攻击流程呢?举一个最简单的例子,比如直接请求http://x.com/del.php?id=1可以删除ID为1的账号,但是只有管理员有这个删除权限,而如果别人在其他某个网站页面加入<img src=”http://x.com/del.php?id=1”>再把这个页面发送给管理员,只要管理员打开这个页面,同时浏览器也会利用当前登录的这个管理员权限发出http://x.com/del.php?id=1这个请求,从而劫持了这个账号做一些攻击者没有权限做的事情。
上面举的这个例子只是其中一个场景,更严重的像添加管理员账号、修改网站配置直接写入webshell等等都有很多案例。
4.3.1 挖掘经验
CSRF主要是用于越权操作,所有漏洞自然在有权限控制的地方,像管理后台、会员中心、论坛帖子以及交易管理等,这几个场景里面,管理后台又是最高危的地方,而CSRF又很少被关注到,因此至今还有很多程序都存在这个问题。我们在挖掘CSRF的时候可以先搭建好环境,打开几个有非静态操作的页面,抓包看看有没有token,如果没有token的话,再直接请求这个页面,不带referer。如果返回的数据还是一样的话,那说明很有可能有CSRF漏洞了,这个是一个黑盒的挖掘方法,从白盒角度来说的话,只要读代码的时候看看几个核心文件里面有没有验证token和referer相关的代码,这里的核心文件指的是被大量文件引用的基础文件,或者直接搜"token"这个关键字也能找,如果在核心文件没有,再去看看你比较关心的功能点的代码有没有验证。
Discuz CSRF备份拖库分析
下面我们来分析一个Discuz CSRF可以直接脱裤的漏洞,这个漏洞影响非常大,漏洞在刚公开的时候导致了大量的Discuz论坛被拖库,漏洞来源乌云缺陷编号:WooYun-2014-64886,作者是跟笔者同一个team(safekey)的matt。
漏洞文件在source/admincp/admincp_db.php第30行开始:
if (! $backupdir ) {$backupdir = random ( 6 ); @mkdir ( './data/backup_'.$backupdir , 0777 );
// 文件夹名是六位随机数 C :: t ( 'common_setting' ) ->update ( 'backupdir' , $backupdir ); /} else {
// 这边也没有做 fromhash 的验证 DB :: query ( 'SET SQL_QUOTE_SHOW_CREATE=0' , 'SILENT' ); if (! $_GET['filename'] || ! preg_match ( '/^[\w\_]+$/' , $_GET['filename'] )) { cpmsg ( 'database_export_filename_invalid' , '' , 'error' ); }/* 省略,往下走 */$backupfilename = './data/'.$backupdir.'/'.str_replace ( array ( '/' , '\\' , '.' , "'" ), '' , $_GET['filename'] ); // 文件名从 $_GET['filename'] )获取,可控 if ( $_GET['usezip'] ) { require_once './source/class/class_zip.php' ; } if ( $_GET['method'] == 'multivol' ) { $sqldump = '' ; $tableid = intval ( $_GET['tableid'] ); $startfrom = intval ( $_GET['startfrom'] ); if (! $tableid && $volume == 1 ) { foreach ( $tables as $table ) {
$sqldump .= sqldumptablestruct ( $table );
} } $complete = TRUE ; for (; $complete && $tableid < count ( $tables ) && strlen ( $sqldump ) + 500 < $_GET ['sizelimit'] * 1000 ; $tableid++ ) { $sqldump .= sqldumptable ( $tables[$tableid] , $startfrom , strlen ( $sqldump )); if ( $complete ) { $startfrom = 0 ; } } $dumpfile = $backupfilename."-%s".'.sql' ; //$dumpfile 为最终导出文件名,下面的代码是写文件
在这个漏洞中,由于表名和文件都是直接GET提交的,目录名由一个固定的backup加上一个六位数字组成,备份成功后可以直接爆破,最终利用可以直接在论坛发帖加入下面代码即可:
<img src="http : //127.0.0.1/discuz/admin.php ? action=db&operation=export&setup=1&scrolltop=&anchor=&type=custom&customtables%5B%5D={ 表名 }&method=multivol&sizelimit=2048&extendins=0&sqlcompat=&usehex=1&usezip=0&filename={ 文件名 }&exportsubmit=%CC%E1%BD%BB22">
利用截图,如图4-9所示。
4.3.2 漏洞防范
防御CSRF漏洞的最主要问题是解决可信的问题,即使是管理员权限提交到服务器的数据,也不一定是完全可信的,所以针对CSRF的防御有以下两点:1)增加token/referer验证避免img标签请求的水坑攻击,2)增加验证码。
4.3.2.1 Token验证
Token翻译中文为“标志”,在计算机认证领域叫令牌。利用验证Token的方式是目前使用的最多的一种,也是效果最好的一种,可以简单理解成在页面或者cookie里面加一个不可预测的字符串,服务器在接收操作请求的时候只要验证下这个字符串是不是上次访问留下的即可判断是不是可信请求,因为如果没有访问上一个页面,是无法得到这个Token的,除非结合XSS漏洞或者有其他手段能获得通信数据。
图4-9 (引用自乌云网)
Token实现测试代码如下:
< ? php
session_start ();
function set_token () {
$_SESSION['token'] = md5 ( time () +rand ( 1 , 1000 ));
}
function check_token () {
if ( isset ( $_POST['token'] ) &&$_POST['token'] === $_SESSION['token'] )
{
return true ;
}
else{
return false ;
}
}
if ( isset ( $_SESSION['token'] ) &&check_token ()) {
echo "success" ;
}
else{
echo "failed" ;
}
set_token ();? >
<form method="post">
<input type="hidden" name="token" value="< ? =$_SESSION['token'] ? >">
<input type="submit"/>
</form>
运行结果,如果请求里面的Token值跟服务器端的一致,则输出“success”,否则输出“failed”。
4.3.2.2 验证码验证
验证码验证没有Token那么实用,考虑到用户体验,不可能让用户每个页面都去输入一次验证码,这估计用户得疯掉,所以一般这种方式只用在敏感操作的页面,比如像登录页面,实现方式跟Token差不多,这里就不再详细给出代码。