0x00前言
为了提高自己的代码审计能力,因此在一个周末遍挑了某CMS进行代码审计,也水了一个CVE
0x01 前台探索
在通读源码之后,发现了一个前台的问题点,在security.class.php
文件里面发现了其后台安全机制,官方说明该cms能够防止密码爆破,并且加入黑名单,对源码进行阅读,截取其中关键地方代码
public function addToBlacklist()
{
$ip = $this->getUserIp();
$currentTime = time();
$numberFailures = 1;
if (isset($this->db['blackList'][$ip])) {
$userBlack = $this->db['blackList'][$ip];
$lastFailure = $userBlack['lastFailure'];
// Check if the IP is expired, then renew the number of failures
if($currentTime <= $lastFailure + ($this->db['minutesBlocked']*60)) {
$numberFailures = $userBlack['numberFailures'];
$numberFailures = $numberFailures + 1;
}
}
$this->db['blackList'][$ip] = array('lastFailure'=>$currentTime, 'numberFailures'=>$numberFailures);
Log::set(__METHOD__.LOG_SEP.'Blacklist, IP:'.$ip.', Number of failures:'.$numberFailures);
return $this->save();
}
跟进一个db的储存方法,在dbjson.class.php中发现其储存的方法
public function save()
{
$data = '';
if ($this->firstLine) {
$data = "<?php defined('BLUDIT') or die('Bludit CMS.'); ?>".PHP_EOL;
}
// Serialize database
$data .= $this->serialize($this->db);
// Backup the new database.
$this->dbBackup = $this->db;
// LOCK_EX flag to prevent anyone else writing to the file at the same time.
if (file_put_contents($this->file, $data, LOCK_EX)) {
return true;
} else {
Log::set(__METHOD__.LOG_SEP.'Error occurred when trying to save the database file.', LOG_TYPE_ERROR);
return false;
}
}
发现其前将内容直接不过滤直接放入到php的文件当中,再来看看该系统是如何获取ip的
public function getUserIp()
{
if (getenv('HTTP_X_FORWARDED_FOR')) {
$ip = getenv('HTTP_X_FORWARDED_FOR');
} elseif (getenv('HTTP_CLIENT_IP')) {
$ip = getenv('HTTP_CLIENT_IP');
} else {
$ip = getenv('REMOTE_ADDR');
}
return $ip;
}
}
因此我们可以通过伪造XFF来进行代码注入,首先进入后台,输入用户名为admin,密码为任意字符
使用XFF进行代码注入
在后台的databases文件夹里面中的security.php
文件中得到注入的文件
但是后期没办法调用该文件,因此留下一个坑,后面慢慢看吧。。。
0x02 后台getshell
既然前台没办法,那么就后台看看呗,同时在测试的时候无意中发现自己上传的php文件被保存到tmp文件夹下,但是因为.htaccess的限制,无法达到其文件夹
AddDefaultCharset UTF-8
<IfModule mod_rewrite.c>
# Enable rewrite rules
RewriteEngine on
# Base directory
RewriteBase /
# Deny direct access to the next directories
RewriteRule ^bl-content/(databases|workspaces|pages|tmp)/.*$ - [R=404,L]
# All URL process by index.php
RewriteCond %{REQUEST_FILENAME} !-f
RewriteCond %{REQUEST_FILENAME} !-d
RewriteRule ^(.*) index.php [PT,L]
</IfModule>
但是猜测我们可以通过上传.htaccess文件对该配置文件进行覆盖,因此来查看文件源码,一路跟进,在upload-image.php文件下面发现蛛丝马迹
$images = array();
foreach ($_FILES['images']['name'] as $uuid=>$filename) {
// Check for errors
if ($_FILES['images']['error'][$uuid] != 0) {
$message = $L->g('Maximum load file size allowed:').' '.ini_get('upload_max_filesize');
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Convert URL characters such as spaces or quotes to characters
$filename = urldecode($filename);
// Move from PHP tmp file to Bludit tmp directory
Filesystem::mv($_FILES['images']['tmp_name'][$uuid], PATH_TMP.$filename);
// Transform the image and generate the thumbnail
$image = transformImage(PATH_TMP.$filename, $imageDirectory, $thumbnailDirectory);
if ($image) {
$filename = Filesystem::filename($image);
array_push($images, $filename);
} else {
$message = $L->g('File type is not supported. Allowed types:').' '.implode(', ',$GLOBALS['ALLOWED_IMG_EXTENSION']);
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
}
跟进一下Filesystem
这个类
public static function mv($oldname, $newname)
{
Log::set('mv '.$oldname.' '.$newname, LOG_TYPE_INFO);
return rename($oldname, $newname);
}
发现这是一个文件移动的方法,即直接将文件移动到tmp目录底下去,因此,可以直接上传.htaccess文件和恶意文件。
上传.htaccess文件
之后在tmp文件目录下面发现该文件
访问目标路径即可以拿到shell
0x03 二次突破
在提交了issue之后,作者马上进行了修复,但是该修复方法非常简单粗暴,即直接在上传文件后进行删除
那么可以使用条件竞争的方式来对文件删除进行绕过,但同时又发现在上传文件的时候,uuid可以进行一个任意文件路径上传
// ----------------------------------------------------------------------------
$uuid = empty($_POST['uuid']) ? false : $_POST['uuid'];
// ----------------------------------------------------------------------------
// Set upload directory
if ($uuid && IMAGE_RESTRICT) {
$imageDirectory = PATH_UPLOADS_PAGES.$uuid.DS;
$thumbnailDirectory = $imageDirectory.'thumbnails'.DS;
if (!Filesystem::directoryExists($thumbnailDirectory)) {
Filesystem::mkdir($thumbnailDirectory, true);
}
} else {
$imageDirectory = PATH_UPLOADS;
$thumbnailDirectory = PATH_UPLOADS_THUMBNAILS;
}
$images = array();
foreach ($_FILES['images']['name'] as $uuid=>$filename) {
// Check for errors
if ($_FILES['images']['error'][$uuid] != 0) {
$message = $L->g('Maximum load file size allowed:').' '.ini_get('upload_max_filesize');
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
// Convert URL characters such as spaces or quotes to characters
$filename = urldecode($filename);
// Check path traversal on $filename
if (Text::stringContains($filename, DS, false)) {
$message = 'Path traversal detected.';
Log::set($message, LOG_TYPE_ERROR);
ajaxResponse(1, $message);
}
可以发现uuid
为一个上传地址的传参,虽然只能上传图片,但是没有进行任何过滤,那么我们可以将图片传如到tmp文件夹下面,然后重复上传.htaccess
文件造成文件竞争即可以进行getshell,更改uuid
得到上传的图片
接着同时上传.htaccess
文件和访问目标图片
最后成功getshell
0x04 后续
作者很快进行了修复
但是问题的根源却还是没有解决,依然可以用两个账号对目标路径进行爆破上传
貌似作者不想修了。。。。
作者终于修好了23333
2023年竟然又给了一个CVE,CVE-2020-20210