metinfo <= 6.2.0前台任意文件上传漏洞GetShell
- 发表于
- Vulndb
Metinfo 存在一个前台任意文件上传漏洞。攻击者可以通过该漏洞直接获取网站权限GetShell,漏洞影响至 Metinfo 最新版,以下是分析文。
漏洞环境
- Windows环境
- PHP版本<=5.3 环境
影响版本
metinfo <= 6.2.0(目前最新版本为 6.2.0 )
漏洞分析
/app/system/include/module/uploadify.class.php
class uploadify extends web {
public $upfile;
function __construct(){
parent::__construct();
global $_M;
$this->upfile = new upfile();
}
uploadify类继承web类, 在构造方法中调用了父类的构造方法, web类是一个前台基类,所以并不会做权限验证则uploadify类无需登录即可使用。
public function doupfile(){
global $_M;
$this->upfile->set_upfile();
$info['savepath'] = $_M['form']['savepath'];
$info['format'] = $_M['form']['format'];
$info['maxsize'] = $_M['form']['maxsize'];
$info['is_rename'] = $_M['form']['is_rename'];
$info['is_overwrite'] = $_M['form']['is_overwrite'];
$this->set_upload($info);
$back = $this->upload($_M['form']['formname']);
if($_M['form']['type']==1){
if($back['error']){
$back['error'] = $back['errorcode'];
}else{
$backs['path'] = $back['path'];
$backs['append'] = 'false';
$back = $backs;
}
}
$back['filesize'] =round(filesize($back['path'])/1024,2);
echo jsonencode($back);
}
$_M[‘form’] 是被metinfo处理后的GPC,所以能够被用户控制。
在该类的doupload方法当中,上传类所用到的部分配置能被用户控制,这里需要关注一下savepath,设置savepath时会被设置为绝对路径,我们可控的点为绝对路径的upload目录之后。
public function set($name, $value) {
if ($value === NULL) {
return false;
}
switch ($name) {
case 'savepath':
$this->savepath = path_standard(PATH_WEB.'upload/'.$value);
在设置完上传的基本配置后,接着调用upload方法。
public function upload($formname){
global $_M;
$back = $this->upfile->upload($formname);
return $back;
}
然后调用upfile对象的upload方法
public function upload($form = '') {
global $_M;
if($form){
foreach($_FILES as $key => $val){
if($form == $key){
$filear = $_FILES[$key];
}
}
}
if(!$filear){
foreach($_FILES as $key => $val){
$filear = $_FILES[$key];
break;
}
}
在upload方法当中, 首先接收_FILES保存到filear变量当中。
$this->getext($filear["name"]); //获取允许的后缀
if (strtolower($this->ext)=='php'||strtolower($this->ext)=='aspx'||strtolower($this->ext)=='asp'||strtolower($this->ext)=='jsp'||strtolower($this->ext)=='js'||strtolower($this->ext)=='asa') {
return $this->error($this->ext." {$_M['word']['upfileTip3']}");
}
if ($_M['config']['met_file_format']) {
if($_M['config']['met_file_format'] != "" && !in_array(strtolower($this->ext), explode('|',strtolower($_M['config']['met_file_format']))) && $filear){
return $this->error($this->ext." {$_M['word']['upfileTip3']}");
}
} else {
return $this->error($this->ext." {$_M['word']['upfileTip3']}");
}
if ($this->format) {
if ($this->format != "" && !in_array(strtolower($this->ext), explode('|',strtolower($this->format))) && $filear) {
return $this->error($this->ext." {$_M['word']['upfileTip3']}");
}
}
接着获取上传文件名的后缀, 首先经过一次黑名单校验然后再继续白名单校验,在这里白名单校验后缀无法绕过所以只能上传以下格式文件
rar|zip|sql|doc|pdf|jpg|xls|png|gif|mp3|jpeg|bmp|swf|flv|ico
//文件名重命名
$this->set_savename($filear["name"], $this->is_rename);
//新建保存文件
if(stripos($this->savepath, PATH_WEB.'upload/') !== 0){
return $this->error($_M['word']['upfileFail2']);
}
if(strstr($this->savepath, './')){
return $this->error($_M['word']['upfileTip3']);
}
if (!makedir($this->savepath)) {
return $this->error($_M['word']['upfileFail2']);
}
在通过白名单校验之后,开始设置文件名,如果this->is_rename为false,那么上传的文件就不会被重命名,而is_rename可以由_M[‘form’][‘is_rename’]控制。
protected function set_savename($filename, $is_rename) {
if ($is_rename) {
srand((double)microtime() * 1000000);
$rnd = rand(100, 999);
$filename = date('U') + $rnd;
$filename = $filename.".".$this->ext;
} else {
$name_verification = explode('.',$filename);
$verification_mun = count($name_verification);
if($verification_mun>2){
$verification_mun1 = $verification_mun-1;
$name_verification1 = $name_verification[0];
for($i=0;$i<$verification_mun1;$i++){
$name_verification1 .= '_'.$name_verification[$i];
}
$name_verification1 .= '.'.$name_verification[$verification_mun1];
$filename = $name_verification1;
}
$filename = str_replace(array(":", "*", "?", "|", "/" , "\\" , "\"" , "<" , ">" , "——" , " " ),'_',$filename);
if (stristr(PHP_OS,"WIN")) {
$filename_temp = @iconv("utf-8","GBK",$filename);
}else
{
$filename_temp = $filename;
}
$i=0;
$savename_temp=str_replace('.'.$this->ext,'',$filename_temp);
while (file_exists($this->savepath.$filename_temp)) {
$i++;
$filename_temp = $savename_temp.'('.$i.')'.'.'.$this->ext;
}
if ($i != 0) {
$filename = str_replace('.'.$this->ext,'',$filename).'('.$i.')'.'.'.$this->ext;
}
}
从该方法中可以看出保护,就算文件名不重命名, 在文件名中含有多个.的情况下, 除了最后一个.其他的都会被替换为_,所以并不能利用。
设置完文件名后, 又开始对this->savepath保存目录进行检验, 同样savepath也可以由_M[‘form’][‘savepath’]设置。首先通过strstr检测路径中是否含有./字符,如果存在直接结束流程,所以也不能使用../进行目录穿越。不过在windows中还可以使用..\实现目录穿越。
接着调用makedir处理目录,
function makedir($dir){
$dir = path_absolute($dir);
@clearstatcache();
if(file_exists($dir)){
$result=true;
}else{
$fileUrl = '';
$fileArr = explode('/', $dir);
$result = true;
foreach($fileArr as $val){
$fileUrl .= $val . '/';
if(!file_exists($fileUrl)){
$result = mkdir($fileUrl);
}
}
}
@clearstatcache();
return $result;
}
makedir方法的作用为判断一个目录是否存在,如果不存在会一层一层的创建目录。在处理完保存路径后,将路径和文件名拼接起来成为上传的目标地址,最终实现上传。
//复制文件
$upfileok=0;
$file_tmp=$filear["tmp_name"];
$file_name=$this->savepath.$this->savename;
if (stristr(PHP_OS,"WIN")) {
$file_name = @iconv("utf-8","GBK",$file_name);
}
if (function_exists("move_uploaded_file")) {
if (move_uploaded_file($file_tmp, $file_name)) {
$upfileok=1;
} else if (copy($file_tmp, $file_name)) {
$upfileok=1;
}
} elseif (copy($file_tmp, $file_name)) {
$upfileok=1;
}
最终的保存文件名由目录和文件名拼接而成,文件名来自_FILES变量,目录来自GPC。在PHP的_FILES文件上传当中,并不存在00截断问题,并且多后缀文件名会被处理,所以这里我们重点关注目录。目录是来自_M[‘form’][‘savepath’]所以用户可控,那么如果存在截断漏洞可以尝试将目录控制为xxx.php\0最终保存路径类似c:/xxx/xxx.php\0/a.jpg实现上传php文件。不过在metinfo当中,在处理GPC保存到_M[‘form’][‘savepath’]时数据会经过addslashes处理,如果这里不会存在00截断问题。
虽然不存在00截断问题,但是在这里可以看到如果系统为windows,在保存文件前对保存路径使用iconv转换了字符集。
iconv truncate
低版本的 PHP 中, iconv 函数存在字符转换截断问题。
首先,程序会根据路径逐级判断目录是否存在。如果不存在,则创建目录。利用了 windows 的特性,构造出 payload: a.php%80\..\1.jpg 。
如果 payload: a.php%80\..\1.jpg 能顺利拼接成文件名,那也就完成了整个漏洞复现。然而在 Metinfo中,在未定义 define('IN_ADMIN', true); ,变量 $_M['form']['is_rename'] 中的数据就会经过 sqlinsert函数处理,而这个函数恰恰把 \ 符号给替换成 / 了,那么就变成了 payload: a.php%80/../1.jpg ,这样就会触发上边的 ./ 匹配规则,所以我们得想办法绕过。
function sqlinsert($string){
if(is_array($string)){
foreach($string as $key => $val) {
$string[$key] = sqlinsert($val);
}
}else{
$string_old = $string;
$string = str_ireplace("\\","/",$string);
$string = str_ireplace("\"","/",$string);
$string = str_ireplace("'","/",$string);
$string = str_ireplace("*","/",$string);
$string = str_ireplace("%5C","/",$string);
$string = str_ireplace("%22","/",$string);
$string = str_ireplace("%27","/",$string);
$string = str_ireplace("%2A","/",$string);
$string = str_ireplace("~","/",$string);
$string = str_ireplace("select", "\sel\ect", $string);
$string = str_ireplace("insert", "\ins\ert", $string);
$string = str_ireplace("update", "\up\date", $string);
$string = str_ireplace("delete", "\de\lete", $string);
$string = str_ireplace("union", "\un\ion", $string);
$string = str_ireplace("into", "\in\to", $string);
$string = str_ireplace("load_file", "\load\_\file", $string);
$string = str_ireplace("outfile", "\out\file", $string);
$string = str_ireplace("sleep", "\sle\ep", $string);
$string = strip_tags($string);
if($string_old!=$string){
$string='';
}
$string = trim($string);
}
return $string;
}
如果之前审计过 Metinfo 的 SQL注入漏洞,就知道可以通过 admin/index.php 来绕过 sqlinsert 函数。我们可以利用该文件中的 define('IN_ADMIN', true) ,然后通过构造 Web 路由完成漏洞触发。
<?php
# MetInfo Enterprise Content Management System
# Copyright (C) MetInfo Co.,Ltd (http://www.metinfo.cn). All rights reserved.
define('IN_ADMIN', true);
$M_MODULE='admin';
if(@$_GET['m'])$M_MODULE=$_GET['m'];
if(@!$_GET['n'])$_GET['n']="index";
if(@!$_GET['c'])$_GET['c']="index";
if(@!$_GET['a'])$_GET['a']="doindex";
@define('M_NAME', $_GET['n']);
@define('M_MODULE', $M_MODULE);
@define('M_CLASS', $_GET['c']);
@define('M_ACTION', $_GET['a']);
require_once '../app/system/entrance.php';
# This program is an open source system, commercial use, please consciously to purchase commercial license.
# Copyright (C) MetInfo Co., Ltd. (http://www.metinfo.cn). All rights reserved.
?>
该文件中,设置了IN_ADMIN常量并且可以自己控制加载的module、class等且无权限验证,所以使用这个文件来加载uploadify类实现上传就能够绕过sqlinsert使用..\实现目录穿越。
metinfo <= 6.2.0 EXP
相关
原文连接
的情况下转载,若非则不得使用我方内容。