ThinkPHP v6.0.x deserialization exploits

Preface:

Made into a letter last big security exercise Cup second CTF game, encountered a problem tp6, he gave the source code, the purpose is to audit the deserialization vulnerability by pop chains.

We summarize here the use of anti-serialization of vulnerabilities tp6.

0x01 environment to build

Now tp new version of the official website is not open source, but you can use composer build environment, the system must first install the composer. Then execute the command:

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

Or can be downloaded go on github, but a lot needs to be changed, people can go to download with a good source on csdn.

tp6 need php7.1 and environment in order to build a more successful.

After the completed structures plus access ip / public can

0x02 conditions of use

A need exists to unserialize () inside the index.php / app root directory / controller function and a controllable point, for example, there

unserialize($_GET['payload'])

0x03POP chain analysis

The overall aim is to track looking for the trigger __toString()point of magic methods

Start with the starting point of __destruct()或__wakeup方法beginning, because they are the trigger points unserialize.

The newly installed system, not downloaded PhpStorm, first use seay audit, then a global search __destruct () method, used here in the Model.php / vendor / topthink / think-orm / src, because it contains save () the method can be triggered.

 

 

 With the inside, when $this->lazySave == true, the $this->save()method will be triggered

$this->lazySave == trueWhen triggered $this->save()method

 

 

 Follow up the save () method

 

 

Find the $ this-> exists properties judge, if true then calls the updateData()method, false is invoked insertData()method.

 Try to follow what updateData () method, found updateDataways to continue to use

 

 

 So we must first construction parameters makes updateData () is triggered, you need to look at this if had lost

 

 

 Follow isEmpty () method to look at,

 

 

 Ensure isEmpty returns false, and $ this-> trigger ( 'BeforeWrite') returns true to bypass the judge then trigger our updateData () method.

Configured $ the this -> Data is non-empty array
Construction $ the this -> withEvent is false
Construction $ the this -> EXISTS is true

Continue to follow up updateData () method

Comprising determining the need to bypass return, before the first determination matches save method, the second non-null $ data if required

 

 

 Attribute value of $ data () is controlled by the method getChangedData, follow it

 

 

 FIG two variables initial value [] if following determination of compliance, return a result, which is the default return $ data = 1, then this step to ensure that we have to bypass checkAllowFields () method of the

Follow checkAllowFields method

I found a string concatenation

 

 

 Since $ this-> field == null, $ this-> schema == null, so the default meet

Then we look at the above db () method

Follow db () method

 

 

 Determine if they meet the conditions, there is $this->table . $this->suffixstitching parameters, it is not that familiar with the two splice it can trigger __toString

Call to connect function meet.

后面就是延续tp5反序列化的触发toString魔术方法了,就是原来vendor/topthink/think-orm/src/model/concern/Conversion.php的__toString开始的利用链

目前的逻辑链图:

 

 

 

 

 

 

 

 

 

 也就是:

__destruct()——>save()——>updateData()——>checkAllowFields()——>db()——>$this->table . $this->suffix(字符串拼接)——>toString()

这一部分构造的参数为:

$this->exists = true;
$this->$lazySave = true;
$this->$withEvent = false;

寻找__toString触发点

全局搜索__toString(),找到vendor/topthink/think-orm/src/model/concern/Conversion.php

 

 跟进toJson()方法

 

 跟进toArray()方法

 

 第三个foreach里面存在getAttr方法,他是个关键方法,我们需要触发他

触发条件:

$this->visible[$key]存在,即$this->visible存在键名为$key的键,而$key则来源于$data的键名,$data则来源于$this->data,也就是说$this->data和$this->visible要有相同的键名$key

构造参数,把$key做为参数传入getAttr方法

跟进getAttrr()方法

 

 然后$key值就传入到了getData()方法,跟进getData方法

 

 第一个if判断传入的值,$key值不为空,因此绕过,然后$key值传入到了getRealFieldName()方法,跟进getRealFieldName方法

 当$this->stricttrue时直接返回$name,即$key

回到getData方法,此时$fieldName = $key,进入判断语句:

 

 返回$this->data[$fielName]也就是$this->data[$key],记为$value,再回到getAttr

 

 也就是返回  $this->getValue($key, $value, null);

跟进getValue()方法

 

只要满足$this->withAttr[$key]存在且$this->withAttr[$key]不为数组就可以触发命令执行。

 最终会把$this->withAttr[$key]作为函数名动态执行函数,而$value作为参数,就可以利用执行系统函数达到命令执行。

到这里呈现了一条完整的POP链。

后半部分__toString的逻辑链图

 

 

 

 

 

 

 

 

 

 这一部分参数赋值:

$this->table = new think\model\Pivot();
$this->data = ["key"=>$command];
$this->visible = ["key"=>1];
$this->withAttr = ["key"=>$function];
$this->$strict = true;

因为这里的Model类为抽象abstract类,所以我们需要找一个继承于Model的类,比如Pivot

0x04 利用exp

tp默认主页目录为/public/,这下面的index.php会定位到/app/controller/index.php文件,因此url中/public/将调用app/controller/index.php,那么我们要知道里面的GET变量,用来传参数。

那么我们定位到app/controller/index.php,找到里面的unserialize()函数里面的GET变量。我这里的环境里面没有unserialize()函数,我们手动加上并加上一个GET变量

 

 网上我看到其他tp6框架的这个文件里面用的是$u这个变量来装unserialize()的值,并且用的GET变量是c,而且还对GET变量进行了base64_decode()编码,我这里为了对应安询杯比赛环境,没有加上base64编码。

首先利用phpggc工具生成exp,phpggc是一个反序列化payload生成工具,大佬们已经将tp6的exp上传到了phpggc,需要安装在linux上,然后执行以下命令生成exp的payload

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

将生成的序列化字符串的payload传参给GET变量c,发送请求,然后将执行phpinfo()。

 

 如果是真实的tp6框架,试试将payload进行base64编码后再发送请求。

 

Guess you like

Origin www.cnblogs.com/-chenxs/p/12020777.html