Metinfo 5.3.19 管理员密码重置漏洞分析

  • A+
所属分类:安全文章 安全漏洞

前言

一个偶然的原因,看到了一个站点,进去后发现有明显的被黑的痕迹,查看整个站点发现采用的是Metinfo 5.3.19 CMS,google一下,发现关于这个版本的CMS有管理员密码重置的漏洞,网上已经有几篇文章来描述这个问题了,这里也顺便参考这自己分析一遍,整理一下整个漏洞的来龙去脉。

Metinfo 5.3.19 管理员密码重置漏洞分析
Metinfo 5.3.19

漏洞分析

在官网上有5.3.19版本下载,下载后发现整个站点的code都拿到手了,看到问题出现在连接 admin/admin/getpassword.php这里,打开这个文件,在第四行

require_once '../include/common.inc.php';

这里直接包含了include/common.inc.php,打开,找到其中关于post的代码,

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

这里存在变量覆盖漏洞,
然后在admin/admin/getpassword.php中查找关于关于重置密码的处理过程

$admin_list = $db->get_one("SELECT * FROM $met_admin_table WHERE admin_id='$admin_mobile' and usertype='3'");
            if($admin_list && $admin_list['admin_email']=='')okinfo('../admin/getpassword.php',$lang_password14);
            if(!$admin_list){
                if(!is_email($admin_mobile))okinfo('../admin/getpassword.php',$lang_password7);
                $admin_list = $db->get_one("SELECT * FROM $met_admin_table WHERE admin_email='$admin_mobile' and usertype='3'");
                if(!$admin_list)okinfo('../admin/getpassword.php',$lang_password14);
            }
            if($admin_list){
                $met_fd_usename=$met_fd_usename;
                $met_fd_fromname=$met_fd_fromname;
                $met_fd_password=$met_fd_password;
                $met_fd_smtp=$met_fd_smtp;
                $met_webname=$met_webname;
                $met_weburl=$met_weburl;
                $adminfile=$url_array[count($url_array)-2];
                $from=$met_fd_usename;
                $fromname=$met_fd_fromname;
                $to=$admin_list['admin_email'];
                $usename=$met_fd_usename;
                $usepassword=$met_fd_password;
                $smtp=$met_fd_smtp;
                $title=$met_webname.$lang_getNotice;
                $x = md5($admin_list[admin_id].'+'.$admin_list[admin_pass]);
                $outime=3600*24*3;
                $String=authcode($admin_list[admin_id].".".$x,'ENCODE'$met_webkeys$outime);
                $String=urlencode($String);
                $mailurl$met_weburl.$adminfile.'/admin/getpassword.php?p='.$String;
                $body ="<style type='text/css'>n";
                $body .="#metinfo{ padding:10px; color:#555; font-size:12px; line-height:1.8;}n";
                $body .="#metinfo .logo{ border-bottom:1px dotted #333; padding-bottom:5px;}n";
                $body .="#metinfo .logo img{ border:none;}n";
                $body .="#metinfo .logo a{ display:block;}n";
                $body .="#metinfo .text{ border-bottom:1px dotted #333; padding:5px 0px;}n";
                $body .="#metinfo .text p{ margin-bottom:5px;}n";
                $body .="#metinfo .text a{ color:#70940E;}n";
                $body .="#metinfo .copy{ color:#BBB; padding:5px 0px;}n";
                $body .="#metinfo .copy a{ color:#BBB; text-decoration:none; }n";
                $body .="#metinfo .copy a:hover{ text-decoration:underline; }n";
                $body .="#metinfo .copy b{ font-weight:normal; }n";
                $body .="</style>n";
                $body .="<div id='metinfo'>n";
                if($met_agents_type<=1){
                    $body .="<div class='logo'><a href='$met_weburl' title='$met_webname'><img src='http://www.metinfo.cn/upload/200911/1259148297.gif' /></a></div>";
                }
                $body .="<div class='text'><p>".$lang_hello.$admin_name."</p><p>$lang_getTip1</p>";
                $body .="<p><a href='$mailurl'>$mailurl</a></p>n";
                if($met_agents_type<=1){
                    $body .="<p>$lang_getTip2</p></div><div class='copy'>$foot</a></div>";
                }
                require_once ROOTPATH.'include/jmail.php';
                $sendMail=jmailsend($from,$fromname,$to,$title,$body,$usename,$usepassword,$smtp);
                if($sendMail==0){
                    require_once ROOTPATH.'include/export.func.php';
                    $post=array('to'=>$to,'title'=>$title,'body'=>$body);
                    $met_file='/passwordmail.php';
                    $sendMail=curl_post($post,30);
                    if($sendMail=='nohost')$sendMail=0;   
                }

                $text=$sendMail?$lang_getTip3.$lang_memberEmail.':'.$admin_list['admin_email']:$lang_getTip4;
                okinfo('../index.php',$text);
            }

注意这里的发送,

require_once ROOTPATH.'include/jmail.php';
                $sendMail=jmailsend($from,$fromname,$to,$title,$body,$usename,$usepassword,$smtp);
                if($sendMail==0){
                    require_once ROOTPATH.'include/export.func.php';
                    $post=array('to'=>$to,'title'=>$title,'body'=>$body);
                    $met_file='/passwordmail.php';
                    $sendMail=curl_post($post,30);
                    if($sendMail=='nohost')$sendMail=0;   
                }

$sendMail==0时,信息会通过变量$post调用curl_post发送出去。curl_post定义在include/export.func.php中:

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.0rn";
                $out .= "Host: $hostrn";
                $out .= "Referer: $met_weburlrn";
                $out .= "Content-type: application/x-www-form-urlencodedrn";
                $out .= "Connection: Closern";
                $out .= "Content-Length: $lenrn";
                $out .="rn";
                $out .= $post."rn";
                fwrite($fp, $out);
                $inheader = 1;     
                while(!feof($fp)){
                    $line = fgets($fp,1024); 
                        if ($inheader == 0) {    
                            $result.=$line;
                        }  
                        if ($inheader && ($line == "n" || $line == "rn")) {  
                            $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';
    }
}

可以发现$met_host表示右键内容发送地点,并且这个参数可以通过变量覆盖进行控制,那么如何让$sendMail==0,触发发送邮件程序呢?这里包含了jmail.php,打开

global $met_fd_port,$met_fd_way;
        $mail             = new PHPMailer();
        //$mail->SMTPDebug  = 3;

        $mail->CharSet    = "UTF-8"// charset
        $mail->Encoding   = "base64";
        $mail->Timeout    = 15
        $mail->IsSMTP(); // telling the class to use SMTP

        //system
        if(stripos($smtp,'.gmail.com')===false){
            $mail->Port       = $met_fd_port;
            $mail->Host       = $smtp; // SMTP server
            if($met_fd_way=='ssl'){
                $mail->SMTPSecure = "ssl";
            }else{
                $mail->SMTPSecure = "";
            }
        }

这里met_fd_port指定了邮件的发送端口,属于系统配置。因此倘若我们利用前面的变量覆盖漏洞修改端口,即可导致邮件发送失败,进入到curl_post

漏洞复现

环境搭建

首先搭建测试环境,本来想在vps上搭建lamp环境然后上传站点的,但是考虑到安全问题,以及各种依赖的升级等等就搁置了。直接在本地Windows部署,然后通过Kali虚拟机和vps测试一下。
首先在本地搭建phpstudy环境。Windows搭建phpstudy还是比较方便的下载下来PHPstudy,解压,几乎是一键式安装啊,安装过后直接启动就好了。

Metinfo 5.3.19 管理员密码重置漏洞分析
phpstudy启动


然后是MetInfo5.3.19部署。这里直接从米拓官网下载历史版本源码即可,下载下来是个zip压缩包,这里讲压缩版保存在安装phpstudy是创建的www路径下,比如E:phpstudyPHPTutorialWWW,直接解压即可。然后在浏览器输入http://127.0.0.1:port这里port为你设置的Apache运行端口,这里可在phpstudy运行管理的端口设置那里进行自主配置。然后浏览器就会出现安装的一系列步骤,按提示来即可。ps.要是想删除重新安装,记得删除/config文件夹下的install.lock


渗透测试

首先在vps上监听80端口nc -lvv 80,应为curl_post使用http协议,默认工作在80端口。

这里直接在kali虚拟机上用burpsuite一把梭就好了。构造找回密码请求如下:

Metinfo 5.3.19 管理员密码重置漏洞分析
找回密码


利用burpsuite构造如下请求,met_host处为用于接收重置链接的vps地址:

Metinfo 5.3.19 管理员密码重置漏洞分析
构造请求


请求发送后,会在vps上收到如下数据信息:

Metinfo 5.3.19 管理员密码重置漏洞分析
收到数据


注意,这里的


http://192.168.1.105:2333/admin/admin/getpassword.php?p=09f7Rd6YXI0qJlu2HY2qe23Prf5j8UAn4Vo8CEXXX9GqAfbj%2BkJGFJUhHeSlkwTBUjtPci0XQX23qgm8pYNupqQsRw

就是密码重置链接,到这里我们就差不多已经成功了。然后就可以登录链接,设置密码,登录后台,

Metinfo 5.3.19 管理员密码重置漏洞分析


Metinfo 5.3.19 管理员密码重置漏洞分析
密码重置


Metinfo 5.3.19 管理员密码重置漏洞分析
登录后台


测试结束。


措施

官方已经放出最新版本,请及时升级。

总结

码代码是要时刻注意变量覆盖危险,做好代码审计和测试工作。


本文始发于微信公众号(星盟安全):Metinfo 5.3.19 管理员密码重置漏洞分析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: