thinkphp8反序列化
前言
摆了一个暑假,正好看见周会有人分析了tp反序列化,想起这条链子的发现者就是我尊敬的nivia,这不得好好分析一下,而且师傅也是分析了这个,所以有了这个文章
链子一 __call触发
分析
相比于我们的6来说,原来我们都是通过save方法来作为入口的,但是8直接将整个类的destruct方法都被删除掉了
这里我们使用6的宁一个类
ResourceRegister#__destruct
我们的目的是触发call方法,所以可以寻找可能触发call的地方,并且参数可以控制
think\Validate#__call
public function __call($method, $args)
{
if ('is' == strtolower(substr($method, 0, 2))) {
$method = substr($method, 2);
}
array_push($args, lcfirst($method));
return call_user_func_array([$this, 'is'], $args);
}
这里解释一下call_user_func_array([$this, ‘is’], $args);这个意思
[$this, 'is']
:这部分表示回调是一个对象方法,$this
是当前对象的引用,'is'
是对象中名为is
的方法。这相当于$this->is()
。$args
:这是一个参数数组,它将被解包并传递给is
方法。
我们看看is方法的重点部分
public function is($value, string $rule, array $data = []): bool
{
$call = function ($value, $rule) {
if (isset($this->type[$rule])) {
// 注册的验证规则
$result = call_user_func_array($this->type[$rule], [$value]);
至于参数怎么控制的,分析完调用链再来研究
回到我们入口它调用了register方法
protected function register()
{
$this->registered = true;
$this->resource->parseGroupRule($this->resource->getRule());
}
可以看到其实这里就有触发
但是参数是不一样的,我们看到call是需要传入两个参数的
而getRule只是返回一个参数
public function getRule()
{
return $this->rule;
}
继续往下来,进入parseGroupRule方法
随便一看,是存在大量的字符串拼接的,是可以触发我们的Tostring的
因为传入参数都是tostring后该考虑的事,这里我们不需要过多关心参数对后面的影响,只需要能够触发tostring就好了
首先一眼看下去是
$rule = implode('/', $item) . '/' . $last;
这里控制last的值更好触发,但是last的来源是
$array = explode('.', $rule);
$last = array_pop($array);
$item = [];
注定了我们的last只能为一个字符串,因为是从我们的rule里面用点分割的,所以不能为一个实例化对象,我们使用
$item[] = $val . '/<' . ($option['var'][$val] ?? $val . '_id') . '>';
这个什么意思呢,问下gpt就懂了
foreach ($array as $val)
:这表示对于数组$array
中的每个元素,将其值赋给变量$val
,然后执行循环体中的代码。$item[] = $val. '/<'. ($option['var'][$val]?? $val. '_id'). '>
:在每次循环中,创建一个新的元素并添加到数组$item
中。这个新元素是由当前的$val
值,加上/<
,再加上$option['var'][$val]
的值(如果存在),如果$option['var'][$val]
不存在,则使用$val. '_id'
,最后加上>
组成。
以下是一个示例:
<?php