环境搭建
使用 composer下载项目
- <code>composer create-project topthink/think=5.1.37 v5.1.37
复制代码
配置控制器
- [/code] 配置路由
- [code]return [
- 'unserialize'=>'index/unserialize/kb',
- ];
复制代码
访问unserialize

反序列化链分析
漏洞链的起点位于
- <code>library\think\process\pipes\Windows.php 的 __destruct()
复制代码
- public function __destruct()
- {
- $this->close();
- $this->removeFiles();
- }
复制代码
跟进removeFiles
- private function removeFiles()
- {
- foreach ($this->files as $filename) {
- if (file_exists($filename)) {
- @unlink($filename);
- }
- }
- $this->files = [];
复制代码
$this->files是个数组 将值循环给到filename,判断文件是否存在,然后将它删除,这里便存在一个任意文件删除漏洞,poc简单自行编写,继续跟进反序列化链

传进来的参数是字符串,如果传进来是个对象的话,那么便会调用对象的__toString方法
全局查找一个__toString方法
thinkphp\library\think\model\concern\Conversion.php
- public function __toString()
- {
- return $this->toJson();
- }
复制代码
跟进toJson
- public function toJson($options = JSON_UNESCAPED_UNICODE)
- {
- return json_encode($this->toArray(), $options);
- }
复制代码
跟进toArray
- public function toArray()
- {
- ***
- // 追加属性(必须定义获取器)
- if (!empty($this->append)) {
- foreach ($this->append as $key => $name) {
- if (is_array($name)) {
- // 追加关联对象属性
- $relation = $this->getRelation($key);
- if (!$relation) {
- $relation = $this->getAttr($key);
- if ($relation) {
- $relation->visible($name);
- }
- }
- ***
- }
复制代码
关键代码就是上面这一段的 $relation->visible( $name); 对象可控参数也可控,方法名不可控,让他调用__call方法即可,if判断中的relation得为假才能进入
跟进getRelation($key)
getRelation在trait RelationShip类中
- public function getRelation($name = null)
- {
- if (is_null($name)) {
- return $this->relation;
- } elseif (array_key_exists($name, $this->relation)) {
- return $this->relation[$name];
- }
- return;
- }
复制代码
这里我们让他返回空即可,跟进getAttr($key)
getAttr在trait Attribute类中
- public function getAttr($name, &$item = null)
- {
- try {
- $notFound = false;
- $value = $this->getData($name);
- } catch (InvalidArgumentException $e) {
- $notFound = true;
- $value = null;
- }
- // 检测属性获取器
- $fieldName = Loader::parseName($name);
- $method = 'get' . Loader::parseName($name, 1) . 'Attr';
- if (isset($this->withAttr[$fieldName])) {
- if ($notFound && $relation = $this->isRelationAttr($name)) {
- $modelRelation = $this->$relation();
- $value = $this->getRelationData($modelRelation);
- }
- $closure = $this->withAttr[$fieldName];
- $value = $closure($value, $this->data);
- } elseif (method_exists($this, $method)) {
- if ($notFound && $relation = $this->isRelationAttr($name)) {
- $modelRelation = $this->$relation();
- $value = $this->getRelationData($modelRelation);
- }
- $value = $this->$method($value, $this->data);
- } elseif (isset($this->type[$name])) {
- // 类型转换
- $value = $this->readTransform($value, $this->type[$name]);
- } elseif ($this->autoWriteTimestamp && in_array($name, [$this->createTime, $this->updateTime])) {
- if (is_string($this->autoWriteTimestamp) && in_array(strtolower($this->autoWriteTimestamp), [
- 'datetime',
- 'date',
- 'timestamp',
- ])) {
- $value = $this->formatDateTime($this->dateFormat, $value);
- } else {
- $value = $this->formatDateTime($this->dateFormat, $value, true);
- }
- } elseif ($notFound) {
- $value = $this->getRelationAttribute($name, $item);
- }
- return $value;
- }
复制代码
最终返回一个value,跟进getData($name),此时的name是上面的append的健
- public function getData($name = null)
- {
- if (is_null($name)) {
- return $this->data;
- } elseif (array_key_exists($name, $this->data)) {
- return $this->data[$name];
- } elseif (array_key_exists($name, $this->relation)) {
- return $this->relation[$name];
- }
- throw new InvalidArgumentException('property not exists:' . static::class . '->' . $name);
- }
复制代码
到这里我们就可以在第二个条件或第三个条件让他返回一个对象了,这时去找一下让它调用哪个对象的__call方法,让它调用Request的__call方法即可,虽说参数不可控,但是调用的函数可控
- public function __call($method, $args)
- {
- if (array_key_exists($method, $this->hook)) {
- array_unshift($args, $this);
- return call_user_func_array($this->hook[$method], $args);
- }
- throw new Exception('method not exists:' . static::class . '->' . $method);
- }
复制代码
小总结
先是调用了trait Conversion的__toString()->toJson()->toArray(),再到trait RelationShip的getRelation,再到trait Attribute的getAttr(),再到getData()返回一个Request对象,所以先得找一个使用了这3个trait类的类

找到了Model但他是个抽象类,再去找找谁继承了它

找到了Pivot,这是可以构造一部分poc了
[code] |