Metinfo 鸡肋getshell分析学习

metinfo存在后台利用curl_post函数的getshell的地方 最新版也没有修复 后来看的时候 发现include/interface/patch.php中使用了此函数 可以设计一下 用相似方法直接前台getshell 但是后来发现 因为前台 与 后台的common.inc.php在伪全局和包含config.inc.php的前后顺序不同 导致不能直接控制met_host

分析1

先来看看patch.php处的漏洞代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
if($action=='patch'){
$met_file='/dl/patch.php';
$post_data = array('ver'=>$metcms_v,'patch'=>$met_patch);
$difilelist=curl_post($post_data,10);
if($difilelist!='nohost'){
$difilelists=explode('*',$difilelist);
$met_file='/dl/olupdate_curl.php';
foreach($difilelists as $key=>$val){
$difilelistss=explode('|',$val);
$met_patch=$difilelistss[0];
unset($difilelistss[0]);
foreach($difilelistss as $key1=>$val1){
$val2=readmin($val1,$met_adminfile,2);
filetest("../../$val2");
$re=dlfile("v$metcms_v/$val1","../../$val2");
if($re!=1){
echo $re;
die();
}
}

其中涉及到两个主要函数 curl_postdlfile函数
curl_post函数:(include/export.func.php)

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
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
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);
}
else{
if(function_exists('fsockopen')||function_exists('pfsockopen')){
$post_data=$post;
$post='';
@ini_set("default_socket_timeout",$timeout);
while (list($k,$v) = each($post_data)) {
$post .= rawurlencode($k)."=".rawurlencode($v)."&";
}
$post = substr( $post , 0 , -1 );
$len = strlen($post);
if(function_exists(fsockopen)){
$fp = @fsockopen($host,80,$errno,$errstr,$timeout);
}
else{
$fp = @pfsockopen($host,80,$errno,$errstr,$timeout);
}
if (!$fp) {
$result='';
}
else {
$result = '';
$out = "POST $file HTTP/1.0\r\n";
$out .= "Host: $host\r\n";
$out .= "Referer: $met_weburl\r\n";
$out .= "Content-type: application/x-www-form-urlencoded\r\n";
$out .= "Connection: Close\r\n";
$out .= "Content-Length: $len\r\n";
$out .="\r\n";
$out .= $post."\r\n";
fwrite($fp, $out);
$inheader = 1;
while(!feof($fp)){
$line = fgets($fp,1024);
if ($inheader == 0) {
$result.=$line;
}
if ($inheader && ($line == "\n" || $line == "\r\n")) {
$inheader = 0;
}
}
while(!feof($fp)){
$result.=fgets($fp,1024);
}
fclose($fp);
str_replace($out,'',$result);
}
}
else{
$result='';
}
}
$result=trim($result);
if(substr($result,0,7)=='metinfo'){
return substr($result,7);
}
else{
return 'nohost';
}
}

curl_psot函数如果对met_config可控 即可控制返回内容 这也是那个后台getshell的重要原因
再来看看dlfile函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
function dlfile($urlfrom,$urlto,$timeout=30){
global $checksum,$metcms_v;
$post_data = array('urlfrom'=>$urlfrom,'checksum'=>$checksum,'metcms_v'=>$metcms_v);
$result=curl_post($post_data,$timeout);
$link=link_error($result);
if($link!=1){
return $link;
}
if(substr($result,-7)=='metinfo'){
$result=substr($result,0,-7);
if($urlto){
$return=file_put_contents($urlto,$result);
if(!$return){return link_error('No filepower');}
else{return 1;}
}
else{
return $result;
}
}else{
return link_error('Timeout');
}
}

dlfile函数使用了file_put_contents函数 内容是通过curl_post获取

所以对patch.php的利用思路是:
控制met_host变量 让curl_post访问自己服务器 在服务器创建 dl目录 目录下创建如下两个文件
Alt text
内容如下(可变通):
Alt text
Alt text
创建目的是因为 整个过程会调用两次curl_post 一次调用 会获取文件名 一次在dlfile调用获取需要写入的文件内容
Alt text
Alt text
即可通过调整服务器上那两个文件内容 来对文件进行覆盖 (1.php是我事先测试创建的文件)

分析2

很美好的想法 并且我们看到前面后台getshell的方法 是可以直接通过get方式传参 来设置met_host的值 最后通过分析 美好的想法可能就终止了
前台和后台都包含一个重要的文件 common.inc.php 但是前台和后台两个文件是不同的 对于这个利用来说 最大的不同在于伪全局和包含config.inc.php的前后顺序不同
伪全局:

1
2
3
4
5
6
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);
}
}

而config.inc.php中有一个关键操作:

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
$_M[config][tablepre]=$tablepre;
$query = "SELECT * FROM $met_config WHERE lang='$lang' or lang='metinfo'";
$result = $db->query($query);
while($list_config= $db->fetch_array($result)){
$_M[config][$list_config['name']]=$list_config['value'];
if($metinfoadminok)$list_config['value']=str_replace('"', '"', str_replace("'", ''',$list_config['value']));
$settings_arr[]=$list_config;
if($list_config['columnid']){
$settings[$list_config['name'].'_'.$list_config['columnid']]=$list_config['value'];
}else{
$settings[$list_config['name']]=$list_config['value'];
}
if($list_config['flashid']){
$list_config['value']=explode('|',$list_config['value']);
$falshval['type']=$list_config['value'][0];
$falshval['x']=$list_config['value'][1];
$falshval['y']=$list_config['value'][2];
$falshval['imgtype']=$list_config['value'][3];
$list_config['mobile_value']=explode('|',$list_config['mobile_value']);
$falshval['wap_type']=$list_config['mobile_value'][0];
$falshval['wap_y']=$list_config['mobile_value'][1];
$met_flasharray[$list_config['flashid']]=$falshval;
}
}
$_M[lang]=$lang;
extract($settings);

这个操作是读取配置数据 内容大致如下:
Alt text
通过执行SELECT * FROM $met_config WHERE lang='$lang' or lang='metinfo 最后整理:
Alt text
可以看到将配置中的键与值都整理到了settings数组中
最后再调用extract函数将内容赋值 可以看到 其中包含met_host键 所以 调用config.inc.php于伪全局代码执行的顺序就决定了我们到底是否可控met_host 这也是此漏洞不能直接利用的原因
可以通过后台 调用config.php:

foreach($settings_arr as $key=>$val){
    if($val['columnid']==$columnid){
        $name = $val['name'];
        $newvalue1 = stripslashes($$val['name']);
        $newvalue1 = str_replace("'","''",$newvalue1);
        $newvalue = str_replace("\\","\\\\",$newvalue1);
        if($val['value']!=$newvalue1){
            $query1 = $columnid?"and columnid='$columnid'":'';
            $query = "update $met_config SET value = '$newvalue' where id ='$val[id]' $query1";
            $db->query($query);
        }
    }
}

对met_host内容进行重置更新利用 但是这样….不说了