某CMS 审计记录

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文件目录下面发现该文件

image

访问目标路径即可以拿到shell

image

 

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

image

得到上传的图片

image

接着同时上传.htaccess文件和访问目标图片

image

最后成功getshell

image

 

0x04 后续

作者很快进行了修复

但是问题的根源却还是没有解决,依然可以用两个账号对目标路径进行爆破上传

image

 

貌似作者不想修了。。。。

 

作者终于修好了23333