新闻详情

分享 | Metinfo 6.1.2 SQL注入漏洞分析

漏洞简介

Metinfo 米拓企业建站系统是一款适合企业网站建设的开源CMS系统,近期互联网上公开爆出 Metinfo 6.1.2 版本存在SQL注入漏洞,随后一秋网络安全团队对其漏洞进行了复现与分析。


漏洞分析

漏洞触发的代码在 /app/system/message/web/message.class.php 文件 第37行,add方法部分。

public function add($info) {
        global $_M;
        if(!$_M[form][id]){
           $message=DB::get_one("select * from {$_M[table][column]} where module= 7 and lang ='{$_M[form][lang]}'");
           $_M[form][id]=$message[id];
        }
        $met_fd_ok=DB::get_one("select * from {$_M[table][config]} where lang ='{$_M[form][lang]}' and  name= 'met_fd_ok' and columnid = {$_M[form][id]}");
        $_M[config][met_fd_ok]= $met_fd_ok[value];
        if(!$_M[config][met_fd_ok])okinfo('javascript:history.back();',"{$_M[word][Feedback5]}");
        if($_M[config][met_memberlogin_code]){
            if(!load::sys_class('pin', 'new')->check_pin($_M['form']['code'])){

                        okinfo(-1, $_M['word']['membercode']);
            }
        }


从上述代码分析发现  $met_fd_ok=DB::get_one("select * from {$_M[table][config]} where lang ='{$_M[form][lang]}' and  name= 'met_fd_ok' and columnid = {$_M[form][id]}");语句中 {$_M[form][id]} 参数,没有用单引号引起来判断此处应该有SQL注入漏洞。

直接拼接SQL 注入payload发现,注入的payload被过滤,无法直接进行SQL注入。继续往下分析。

继续分析发现 message.class.php 文件 调用了 class feedback extends web  父类初始化函数,跟进web类。

web类定义在  /app/system/include/class/web.class.php 文件。分析web类,没有发现对参数进行过滤的函数,但是发现初始化了 common类。 跟进common类,定义在/app/system/include/class/common.class.php文件。

public function __construct() {
        global $_M;//全局数组$_M
        ob_start();//开启缓存
        $this->load_mysql();//数据库连接
        $this->load_form();//表单过滤
        $this->load_lang();//加载语言配置
        $this->load_config_global();//加载全站配置数据
        $this->load_url_site();
        $this->load_config_lang();//加载当前语言配置数据
        $this->load_url();//加载url数据
    }


发现问题关键点,$this->load_form();//表单过滤,继续跟进$this->load_form();函数。

/app/system/include/class/common.class.php文件51行、

protected function load_form() {
        global $_M;
        $_M['form'] =array();
        isset($_REQUEST['GLOBALS']) && exit('Access Error');
        foreach($_COOKIE as $_key => $_value) {
            $_key{0} != '_' && $_M['form'][$_key] = daddslashes($_value);
        }
        foreach($_POST as $_key => $_value) {
            $_key{0} != '_' && $_M['form'][$_key] = daddslashes($_value);
        }
        foreach($_GET as $_key => $_value) {
            $_key{0} != '_' && $_M['form'][$_key] = daddslashes($_value);
        }
        if(is_numeric($_M['form']['lang'])){//伪静态兼容
            $_M['form']['page'] = $_M['form']['lang'];
            $_M['form']['lang'] = '';
        }
        if($_M['form']['metid'] == 'list'){
            $_M['form']['list'] = 1;
            $_M['form']['metid'] = $_M['form']['page'];
            $_M['form']['page'] = 1;
        }
        if(!preg_match('/^[0-9A-Za-z]+$/', $_M['form']['lang']) && $_M['form']['lang']){
            echo "No data in the database,please reinstall.";
            die();
        }
    }


这里看到把 POST、GET、COOKIE 传递过来的参数用daddslashes进行了全局过滤。跟进 daddslashes


function daddslashes($string, $force = 0) {
    !defined('MAGIC_QUOTES_GPC') && define('MAGIC_QUOTES_GPC', get_magic_quotes_gpc());
    if(!MAGIC_QUOTES_GPC || $force) {
        if(is_array($string)) {
            foreach($string as $key => $val) {
                $string[$key] = daddslashes($val, $force);
            }
        } else {
           if(!defined('IN_ADMIN')){
                $string = trim(addslashes(sqlinsert($string)));
            }else{
                $string = trim(addslashes($string));
            }
        }
    }
    return $string;
}


这里判断是否开启了get_magic_quotes_gpc() ,如果没开启或者 $force不为空就进入下面的逻辑。有判断了是否定义IN_ADMIN 常量,如果没有定义就进入 $string = trim(addslashes(sqlinsert($string)));,从代码逻辑发现我们第一步进行SQL注入,失败是因为注入的payload被过滤导致,那么我们传递的值应该就是进入了这个过滤,用sqlinsert函数进行了过滤。

看一下sqlinsert函数是怎么进行的过滤。

function sqlinsert($string){
    if(is_array($string)){
        foreach($string as $key => $val) {
            $string[$key] = sqlinsert($val);
        }
    }else{
        $string_old = $string;
        $string = str_ireplace("\\\\","/",$string);
        $string = str_ireplace("\\"","/",$string);
        $string = str_ireplace("'","/",$string);
        $string = str_ireplace("*","/",$string);
        $string = str_ireplace("%5C","/",$string);
        $string = str_ireplace("%22","/",$string);
        $string = str_ireplace("%27","/",$string);
        $string = str_ireplace("%2A","/",$string);
        $string = str_ireplace("~","/",$string);
        $string = str_ireplace("select""\\sel\\ect", $string);
        $string = str_ireplace("insert""\\ins\\ert", $string);
        $string = str_ireplace("update""\\up\\date", $string);
        $string = str_ireplace("delete""\\de\\lete", $string);
        $string = str_ireplace("union""\\un\\ion", $string);
        $string = str_ireplace("into""\\in\\to", $string);
        $string = str_ireplace(<span class="hljs-string" style="f