作者:wh1t3p1g

来源:https://www.anquanke.com/post/id/187332

0x00 前言

上周参与了N1CTF,里面有一道关于thinkphp5的反序列化漏洞的利用。记录一下关于该反序列化的利用链分析。

后文主要包括两条链的利用分析(一条是我找的,也是题目的预期解,另一条是wonderkun师傅找的非预期解)

0x01 环境准备

这里就不直接用题目的环境了,采用composer直接安装5.2.*-dev版本

composer create-project topthink/think=5.2.x-dev v5.2

0x02 利用链分析

背景回顾

tp5在我印象里反序列化的利用链存在一个Windows类的任意文件删除,但是在这篇文章的启示下,也算是找到了一条新的路(关于__toString的触发方式,除了字符串拼接的方式,还可以利用PHP自带函数参数的强制转换)。

这篇文章的利用链最后用到了5.1.37版本think/Request.php的 __call函数,该函数的调用函数名可控,所以可以导致任意调用其他的类函数。而在5.2.x版本不存在这样的一个__call函数,这意味着我们需要重新找一个最终达成命令执行的函数调用(__call函数前的利用链仍然可用)。

那么接下来,我们来看看5.2.x新的利用链吧:)

think/model/concern/Attribute.php getValue可函数动态调用函数(题目的预期解)

由于5.1.37__call函数前的利用链仍然存在于5.2.x版本,这里就不再详述了。

先来看一下Conversion类的toArray函数

去掉了无关的代码,这里this −  > visiblethis->relation均可控,可伪造数据进入getAttr函数

直接关注getValue函数,该函数可动态调用函数,并且调用函数、函数参数均可控。所以接下来有两种方法,第一种是找一个符合条件的php函数,另一种是利用tp自带的SerializableClosure调用,来看一下第二种。

可用于序列化匿名函数,使得匿名函数同样可以进行序列化操作。这意味着我们可以序列化一个匿名函数,然后交由上述的closure(value, $this->data)调用执行。

以上述代码为例,将调用phpinfo函数。

到此为止,我们分析完了整一个调用流程,回顾一下

1.vendor/topthink/framework/src/think/process/pipes/Windows.php __destruct ->removeFiles ->file_exists 强制转化字符串filename,这里的filename可控 可触发__toString函数,下一步找可利用的__toString

2.vendor/topthink/framework/src/think/model/concern/Conversion.php __toString -> toJson -> toArray创造符合条件的relation和visible-> getAttr 下一步调用vendor/topthink/framework/src/think/model/concern/Attribute.php的getValue函数

3.vendor/topthink/framework/src/think/model/concern/Attribute.php getValue -> $closure动态调用函数,且该内容可控 下一步利用有两种,一种找符合的php函数,另一种利用tp自带的SerializableClosure调用

4.vendor/opis/closure/src/SerializableClosure.php 构造可利用的匿名函数

我把exp集成到了phpggc上,使用如下命令即可生成

./phpggc -u ThinkPHP/RCE1 'phpinfo();'

这里由于用到了SerializableClosure,需要使用编码器编码,不可直接输出拷贝利用。

think/Db.php __call函数可实例化任意类(题目的非预期解)

前面说到5.1.37版本的利用链的__call函数,在5.2.x版本没办法用了。但是从__destruct到__call的链路是通的,我们只需要重新找一个可用的__call函数即可。

来看一下vendor/topthink/framework/src/think/Db.php的__call函数

this −  > configthis->connection均可控,这意味着我们可以实例化任意符合条件的类,这里找了think

该构造器引入了RuntimePath下的route.php文件,因为这道题是允许上传文件的,所以只要在可上传的目录下上传一个route.php的webshell即可。至于RuntimePath,appruntimePath的内容即可。

我们直接构造App对象为

这个构造思路太溜了,膜一波:)

整理一下过程

1.vendor/topthink/framework/src/think/process/pipes/Windows.php __destruct ->removeFiles ->file_exists 强制转化字符串filename,这里的filename可控 可触发__toString函数,下一步找可利用的__toString

2.vendor/topthink/framework/src/think/model/concern/Conversion.php

__toString -> toJson -> toArray->appendAttrToArray->$relation调用不存在的函数,触发__call

3.vendor/topthink/framework/src/think/Db.php

__call -> new class(this->connection) 调用任意类的__construct函数

4.vendor/topthink/framework/src/think/Url.php

构造App类,达到include任意文件的效果

0x00 前言

上一篇分析了tp 5.2.x的反序列化利用链挖掘,顺着思路,把tp6.0.x也挖了。有类似的地方,也有需要重新挖掘的地方。

0x01 环境准备

采用composer安装6.0.*-dev版本

composer create-project topthink/think=6.0.x-dev v6.0

0x02 利用链分析

背景回顾

拿到v6.0.x版本,简单的看了一下,有一个好消息和一个坏消息。

好消息是5.2.x版本函数动态调用的反序列化链后半部分,还可以利用。

坏消息是前面5.1.x,5.2.x版本都基于触发点Windows类的__destruct,好巧不巧的是6.0.x版本取消了Windows类。这意味着我们得重新找一个合适的起始触发点,才能继续使用上面的好消息。

vendor/topthink/think-orm/src/Model.php 新起始触发点

为了节省篇幅,后文不再重复介绍触发__toString函数后的利用链,这部分同5.2.x版本相同(不过wonderkun师傅的利用链已失效,动态函数调用的利用链还能用)。

通常最好的反序列化起始点为__destruct、__wakeup,因为这两个函数的调用在反序列化过程中都会自动调用,所以我们先来找此类函数。这里我找了vendor/topthink/think-orm/src/Model.php的__destruct函数。

首先构造lazySave的值为true,从而进入save函数。

这次触发点位于updateData函数内,为了防止前面的条件符合,而直接return,我们首先需要构造相关参数

其中需保证isEmpty返回false,以及$this->trigger(‘BeforeWrite’)返回true

1.构造$this->data为非空数组

2.构造$this->withEvent为false

3.构造$this->exists为true

从而进入我们需要的updateData函数,来看一下该函数内容

同样的,为了防止提前return,需要符合$data非空,来看一下getChangedData

这里我们可以强行置this −  > forcetruethis->data

这样,我们就成功到了调用checkAllowFields的位置

同样,为了到$this->db()函数的调用,需要

1.构造$this->field为空

2.构造$this->schema为空

其实这两个地方不需要构造,默认都为空

最终,我们终于到了可以触发__toString的位置

看到熟悉的字符串拼接了嘛!!!

不过为了达到该出拼接,我们还是得首先满足connect函数的调用。此处代码就不说了,置this −  > connectionmysqlthis->name还是$this->suffix为最终的触发__toString的对象,都会有同样的效果。

后续的思路,就是原来vendor/topthink/think-orm/src/model/concern/Conversion.php的__toString开始的利用链,不在叙述。

我把exp集成到了phpggc上,使用如下命令即可生成

./phpggc -u ThinkPHP/RCE2 'phpinfo();'

这里由于用到了SerializableClosure,需要使用编码器编码,不可直接输出拷贝利用。