本文作者:1x2Bytes(信安之路红蓝对抗小组成员)
6.0.0 中有两个版本存在该漏洞, dev 版本只能覆盖任意位置的文件,6.0.0-1 则可以在特定的情况下控制写入的内容实现 getshell,看到一些师傅的 blog 的文章使用 composer 下载的源码, Thinkphp6 也确实开始使用 composer 的方式进行安装但是我使用 composer 方式下载的源码无法复现,猜测进行了修复,于是在网上找一键安装包,找了半天找到一个 11 月份的版本遂复现成功.`
具体漏洞位置:
在vendortopthinkframeworksrcthinksessionStore.php
文件254
行开始
public function save(): void
{
$this->clearFlashData();
$sessionId = $this->getId();
if (!empty($this->data)) {
$data = $this->serialize($this->data);
$this->handler->write($sessionId, $data);
} else {
$this->handler->delete($sessionId);
}
$this->init = false;
}
这里 $this->handler->write($sessionId, $data)
是漏洞的关键位置,handler
的值我们从文件开头 53 行的__construct
方法中可以看到 handler 是 SessionHandlerInterface 接口
我们搜索 SessionHandlerInterface
分别发现 File 类与 Cache 类都实现了该接口, 查看了 Cache 的 write 方法,并没有进行文件写入的操作,于是分析 File 中的 write 方法,看注释应该是跟 Session 操作相关,在文件vendortopthinkframeworksrcthinksessiondriverFile.php
的 210 行
$filename
变量是从 getFileName 方法中获取,传入的值为 $sessID, 跟进该方法,在 File 文件的 117 行
protected function getFileName(string $name, bool $auto = false): string
{
if ($this->config['prefix']) {
// 使用子目录
$name = $this->config['prefix'] . DIRECTORY_SEPARATOR . 'sess_' . $name;
} else {
$name = 'sess_' . $name;
}
$filename = $this->config['path'] . $name;
$dir = dirname($filename);
if ($auto && !is_dir($dir)) {
try {
mkdir($dir, 0755, true);
} catch (Exception $e) {
// 创建失败
}
}
return $filename;
}
这里判断是否有配置 session 文件的前缀,配置文件在config/session.php
,如果存在配置则拼接到路径的最后并在 $name 前加上字符串sess_
,不存在则直接拼接sess_
前缀后返回文件名,最后 write 方法进行了 writeFile 操作,跟进 writeFile 方法,在文件 170 行进入 file_put_contents 操作,其中的文件名和内容我们都可控,我们下一步要查看如何控制我们写入的值和文件名
回到前面的 save 方法,传入的$sessionId
变量是 getId 方法获取的,查看 getId 方法
该方法返回 id 的值,该值已经在 setId 方法中进行设置,于是查看 119 行的 setld 方法
代码语言:javascript复制 public function setId($id = null): void
{
$this->id = is_string($id) && strlen($id) === 32 ? $id : md5(microtime(true) . session_create_id());
}
这里对 $id 的值进行了判断长度是否为 32 位,所以构造 payload 的时候要注意长度为 32
查找使用 setId 方法的文件,在vendortopthinkframeworksrcthinkmiddlewareSessionInit.php
46 行
$varSessionId 变量的值从配置中获取session.var_session_id
的值,因为 session.var_session_id
默认是空 ,所以进入另一分支$sessionId
变量的值由$request->cookie($cookieName)
获取, $cookieName 由 $this->session->getName() 获取,查看 getName 方法
返回的值为 name,查看 name 变量的值在 Store 文件 36 行已经赋值,为 PHPSESSID
复现的时候要在 app/middleware.php 文件中开启即去除注释 thinkmiddlewareSessionInit::class
然后在控制器中使用 Thinkphp 的 session 方法设定值,在 Index 控制器中修改 index 方法
public function index()
{
if($_GET['code']){
session('test', $_GET['code']);
return 'ThinkPHP V6.0.0';
}
}
搭建好后使用以下 Payload:
代码语言:javascript复制../../../../testgetshellvuln.php //在根目录下写入文件
../../../../public/shellvuln.php //写入public
成功 getshell