前言
一个偶然的原因,看到了一个站点,进去后发现有明显的被黑的痕迹,查看整个站点发现采用的是Metinfo 5.3.19 CMS,google一下,发现关于这个版本的CMS有管理员密码重置的漏洞,网上已经有几篇文章来描述这个问题了,这里也顺便参考这自己分析一遍,整理一下整个漏洞的来龙去脉。
漏洞分析
在官网上有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,解压,几乎是一键式安装啊,安装过后直接启动就好了。
然后是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一把梭就好了。构造找回密码请求如下:
利用burpsuite构造如下请求,met_host
处为用于接收重置链接的vps地址:
请求发送后,会在vps上收到如下数据信息:
注意,这里的
http://192.168.1.105:2333/admin/admin/getpassword.php?p=09f7Rd6YXI0qJlu2HY2qe23Prf5j8UAn4Vo8CEXXX9GqAfbj%2BkJGFJUhHeSlkwTBUjtPci0XQX23qgm8pYNupqQsRw
就是密码重置链接,到这里我们就差不多已经成功了。然后就可以登录链接,设置密码,登录后台,
测试结束。
措施
官方已经放出最新版本,请及时升级。
总结
码代码是要时刻注意变量覆盖危险,做好代码审计和测试工作。
本文始发于微信公众号(星盟安全):Metinfo 5.3.19 管理员密码重置漏洞分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论