• 售前

  • 售后

热门帖子
入门百科

Thinkphp-v5.1.x反序列化漏洞复现分析

[复制链接]
蠕行者 显示全部楼层 发表于 2022-1-15 10:52:08 |阅读模式 打印 上一主题 下一主题
环境搭建

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

反序列化链分析

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

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

先是调用了trait Conversion的__toString()->toJson()->toArray(),再到trait RelationShip的getRelation,再到trait Attribute的getAttr(),再到getData()返回一个Request对象,所以先得找一个使用了这3个trait类的类

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

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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有帐号?立即注册

x

帖子地址: 

回复

使用道具 举报

分享
推广
火星云矿 | 预约S19Pro,享500抵1000!
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

草根技术分享(草根吧)是全球知名中文IT技术交流平台,创建于2021年,包含原创博客、精品问答、职业培训、技术社区、资源下载等产品服务,提供原创、优质、完整内容的专业IT技术开发社区。
  • 官方手机版

  • 微信公众号

  • 商务合作