pop链的学习到thinkphp5.x反序列化

pop链的学习到thinkphp5.x反序列化

前言

第一次接触序列化的时候还是ctf,没有清晰的思路,今天复现thinkphp反序列化漏洞时看到一篇文章结合代码审计感觉对我用处很大,就记下来。

起点

也就是我们需要找到自动调用的方法,即魔术方法

  • __ destruct
  • __ wakeup

因为也是第一次真正意义上学习如何构造pop链, __ wakeup()方法ctf里挺常见的,审计的时候还是__ destruct里用得多,就拿thinkphp反序列化来说吧,起点就是

thinkphp/library/think/process/pipes/windows.php

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

这里__ destruct()魔术方法自动调用了本类中的两个函数,也就是开始构造pop链的起点,close()方法用处不大,跟进removeFile()方法

    private function removeFiles()
    {
        foreach ($this->files as $filename) {
            if (file_exists($filename)) {
                @unlink($filename);
            }
        }
        $this->files = [];
    }

此函数的作用是删除临时文件,其中filename变量是我们可控的,而且file_exists函数是操作字符串的函数,两个条件都有了,接下来就是寻找跳板的问题

跳板

所谓的跳板,就是在方法和方法、结构和结构、方法和结构之间的跳跃。

然后就会涉及到一些魔法方法和回调方法,比如

__ call()方法,此方法是在类外调用一个类中不可访问的方法时,就会触发__ call()方法,从而可能能利用到的函数

还有call_user_func($this->test)或者call_user_func_array((new test), "aaa")也可以当作跳板

以及new $test($test1$, $test2)可以调用__construct()方法

最后就是等下要涉及到的__ tostring魔法方法,当遇到一些处理字符串的函数时,并且变量我们可控时,可以将其赋值为存在此方法的对象即能调用此方法。

__ tostring作跳板


thinkphp/library/think/model.php

    public function __toString()
    {
        return $this->toJson();
    }

可以发现在model.php中存在此方法,并且可以进一步利用,调用tojson方法----进而调用toarray方法

__ call方法作跳板

toarray()方法中最后一段代码涉及到对象的引用,并且该变量我们可控

					if (method_exists($this, $relation)) {
                        $modelRelation = $this->$relation();
                        $value         = $this->getRelationData($modelRelation);

                        if (method_exists($modelRelation, 'getBindAttr')) {
                            $bindAttr = $modelRelation->getBindAttr();
                            if ($bindAttr) {
                                foreach ($bindAttr as $key => $attr) {
                                    $key = is_numeric($key) ? $attr : $key;
                                    if (isset($this->data[$key])) {
                                        throw new Exception('bind attr has exists:' . $key);
                                    } else {
                                        $item[$key] = $value ? $value->getAttr($attr) : null;//value变量可控

上层代码我就不分析了,和__ tostring的利用方式类似,赋值给value一个不存在getAttr方法的对象,并且有机会利用
这里利用output类中的方法


thinkphp/library/think/console/output.php
    public function __call($method, $args)
    {
        if (in_array($method, $this->styles)) {
            array_unshift($args, $method);
            return call_user_func_array([$this, 'block'], $args);
        }

        if ($this->handle && method_exists($this->handle, $method)) {
            return call_user_func_array([$this->handle, $method], $args);
        } else {
            throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
        }
    }
}


这里回调方法调用本类中的block()方法,进去康康:

    protected function block($style, $message)
    {
        $this->writeln("<{$style}>{$message}</$style>");
    }

我有预感又是一系列的调用过程,然后最后会到write()方法中


    public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
    {
        $this->handle->write($messages, $newline, $type);
    }

在我们进行序列化时$this->handle是我们可控的,可以找外部类中存在write方法的类,可以进行全局搜索这里我们找到的是think/session/driver/memcached.php

    public function write($sessID, $sessData)
    {
        return $this->handler->set($this->config['session_name'] . $sessID, $sessData, $this->config['expire']);
    }

好像灭有啥,这里我们还是可控的同样的方式找set方法/think/cache/driver/file.php

    public function set($name, $value, $expire = null)
    {
        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }
        if ($expire instanceof \DateTime) {
            $expire = $expire->getTimestamp() - time();
        }
        $filename = $this->getCacheKey($name, true);
        if ($this->tag && !is_file($filename)) {
            $first = true;
        }
        $data = serialize($value);
        if ($this->options['data_compress'] && function_exists('gzcompress')) {
            //数据压缩
            $data = gzcompress($data, 3);
        }
        $data   = "<?php\n//" . sprintf('%012d', $expire) . "\n exit();?>\n" . $data;
        $result = file_put_contents($filename, $data);
        if ($result) {
            isset($first) && $this->setTagItem($filename);
            clearstatcache();
            return true;
        } else {
            return false;
        }

好了到这里就可以写入文件了,并且这里参数都是可控的。

终点

终点当然就是利用危险函数进行写shell或者RCE咯
https://blog.csdn.net/qq_43571759/article/details/105919851

这是部分PHP危险函数的总结

大致就到这里,其实还准备了thinphp5.1的漏洞分析,调用过程也差不太多,后半段和thinkphp5.1的RCE利用链差不多,就不写了。

最后

也只是学到一定皮毛,还会继续加油。刚刚并没有说完,没有说咋去构造poc,还有最后写文件的地方filename利用为协议绕过exit,$data值比较棘手,最后是通过文件名写入shell的

参考文章:
https://www.anquanke.com/post/id/196364#h2-3
https://xz.aliyun.com/t/8082

posted @ 2020-08-21 22:29  h00gry  阅读(276)  评论(0)    收藏  举报