β

Metinfo 5.3.17 前台SQL注入漏洞分析

离别歌 45 阅读
 

Metinfo 8月1日升级了版本,修复了一个影响小于等于5.3.17版本(几乎可以追溯到所有5.x版本)的SQL注入漏洞。这个SQL注入漏洞不受软WAF影响,可以直接获取数据,影响较广。

0x01. 漏洞原理分析

漏洞出现在 /include/global.func.php 文件的 jump_pseudo 函数:

<?php
/*静态页面跳转*/
function jump_pseudo(){
    global $db,$met_skin_user,$pseudo_jump;
    global $met_column,$met_news,$met_product,$met_download,$met_img,$met_job;
    global $class1,$class2,$class3,$id,$lang,$page,$selectedjob;
    global $met_index_type,$index,$met_pseudo;
    if($met_pseudo){
        $metadmin[pagename]=1;
        $pseudo_url=$_SERVER[HTTP_X_REWRITE_URL]?$_SERVER[HTTP_X_REWRITE_URL]:$_SERVER[REQUEST_URI];
        $pseudo_jump=@strstr($_SERVER['SERVER_SOFTWARE'],'IIS')&&$_SERVER[HTTP_X_REWRITE_URL]==''?1:$pseudo_jump;
        $dirs=explode('/',$pseudo_url);
        $dir_dirname=$dirs[count($dirs)-2];
        $dir_filename=$dirs[count($dirs)-1];
        if($pseudo_jump!=1){
            $dir_filenames=explode('?',$dir_filename);
            switch($dir_filenames[0]){
                case 'index.php':
                    if(!$class1&&!$class2&&!$class3){
                        if($index=='index'){
                            if($lang==$met_index_type){
                                $jump['url']='./';
                            }else{
                                $jump['url']='index-'.$lang.'.html';
                            }
                        }else{
                            if($lang==$met_index_type){
                                $jump['url']='./';
                            }else{
                                $id=$class3?$class3:($class2?$class2:$class1);
                                if($id){
                                    $query="select * from $met_column where id='$id'";
                                }else{
                                    $query="select * from $met_column where foldername='$dir_dirname' and lang='$lang' and (classtype='1' or releclass!='0') order by id asc";
                                }
                                $jump=$db->get_one($query);
                                $psid= ($jump['filename']<>"" and $metadmin['pagename'])?$jump['filename']:$jump['id'];
                                if($jump[module]==1){
                                    $jump['url']='./'.$psid.'-'.$lang.'.html';
                                }else if($jump[module]==8){
                                    $jump['url']='./'.'index-'.$lang.'.html';
                                }
                                else{
                                    if($page&&$page!=1)$psid.='-'.$page;
                                    $jump['url']='./'.'list-'.$psid.'-'.$lang.'.html';
                                }
                            }
                        }
                        ...
}

代码截的不全,只关注一下这几个操作:

  1. $pseudo_url=$_SERVER[HTTP_X_REWRITE_URL]?$_SERVER[HTTP_X_REWRITE_URL]:$_SERVER[REQUEST_URI]; : 从 $_SERVER[HTTP_X_REWRITE_URL] 中获取 $pseudo_url 变量
  2. $dirs=explode('/',$pseudo_url); :将 $pseudo_url 变量用斜线分割成 $dirs 数组
  3. $dir_dirname=$dirs[count($dirs)-2]; :获取 $dirs 的倒数第二个元素作为 $dir_dirname 变量
  4. $query="select * from $met_column where foldername='$dir_dirname' and lang='$lang' and (classtype='1' or releclass!='0') order by id asc"; $dir_dirname 变量被拼接进SQL语句

所以,通过分析可知, $_SERVER[HTTP_X_REWRITE_URL] 的一部分,最终被拼接进SQL语句。那么,如果Metinfo没有对HTTP头进行验证的情况下,将导致一个SQL注入漏洞。

看一下Metinfo对于变量的获取方式:

<?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);
    }
}

使用daddslashes函数过滤GPC变量,daddslashes这个函数确实很讨厌,不光有转义,而且有很不友好的软WAF。但我们这里这个注入点是来自于SERVER变量,所以是不受软WAF影响的。

0x02. 漏洞利用缺陷

那么,我们看看如何才能进入这个注入的位置。

jump_pseudo 函数前面有一些条件语句,归纳一下主要有下面几个:

  1. 需要满足 if($met_pseudo)...
  2. 需要满足 if($pseudo_jump!=1)...
  3. 需要满足 switch($dir_filenames[0]){ case 'index.php':...
  4. 需要满足 if(!$class1&&!$class2&&!$class3)...
  5. 不能满足 if($index=='index')...
  6. 不能满足 if($lang==$met_index_type)...

翻译成汉字,大意就是:

  1. $met_pseudo 必须为真。 $met_pseudo 这个变量是指系统是否开启了伪静态,也就说这个漏洞需要开启伪静态才能够利用。
  2. $pseudo_jump 不等于1。这个条件,只要 $_SERVER[HTTP_X_REWRITE_URL] 有值即可满足。
  3. $dir_filenames[0] 必须等于 'index.php' ,这个变量是可控的。
  4. class1 class2 class3 不能有值。这个条件,只要我访问的是index.php,并且不主动传入这三个参数,即可满足。
  5. $index 不能等于 'index' ,这个变量也是可控的,传入参数 index=xxxx 即可
  6. $lang 不能等于 $met_index_type

这6个条件语句中,2~5中的变量都可控,1中的变量只要开启伪静态即可满足,唯独6需要单独分析一下。

$lang 是我们传入的参数,代表给访客显示的语言是什么。Metinfo默认安装时,将存在3种语言:简体中文(cn)、英文(en)、繁体中文(tc),而 $met_index_type 表示默认语言类型,默认是中文,也就是cn。

而Metinfo的配置(包括伪静态相关的配置),是和语言有关系的,不同语言的配置不相同。默认情况下,如果管理员在后台开启伪静态,将只会修改 lang=cn 时的配置。

那么,正常情况下,我们传入 index.php?lang=cn ,将会导致 if($lang==$met_index_type)... 这个条件成立,也就没法进入SQL注入的语句中;如果我们传入 index.php?lang=en ,又导致伪静态配置恢复默认,也就是 $met_pseudo = 0 ,导致进不去步骤1的if语句;如果我们传入一个不存在的lang,比如 index.php?lang=xxx ,将会导致报错: No data in the database,please reinstall.

这就比较蛋疼。此时,就需要利用到Mysql的一个特性。

0x03. Mysql 大小写特性回顾

Mysql对于内容的存储方式,有如下两个概念:字符集(character set)和collation(比对方法)。

二者组合成Mysql的字符格式,一般来说分为这两类:

  1. <character set>_<language/other>_<ci/cs>
  2. <character set>_bin

比如,最常用的 utf8_general_ci ,就是第一种格式。

我们这里需要关注的就是最后一串: ci cs bin ,这三个究竟是什么?

ci 其实就是 case insensitive (大小写不敏感)的缩写, cs 是 case sensitive (大小写敏感)的缩写。也就是说,当我们用的字符格式是 utf8_general_ci 时,Mysql中比对字符串的时候是大小写不敏感的。

bin 指的是比较的时候,按照二进制的方式比较,这种情况下就不存在大小写的问题了。 bin 方式还可以解决有些小语种上的特性,这个就不展开说了。

我们随便找了个数据表,做个小实验:

15021068924109.jpg

可见上图,虽然我查询的SQL语句是 SELECT * FROM `wp_users` WHERE `user_login`='AdmIN' ,但实际上查询出来了用户名是admin的用户账户。

0x04. 完成漏洞利用

回到Metinfo,我们可以利用0x03中说到的Mysql特点,来绕过 if($lang==$met_index_type)... 的判断。

我们来看看Metinfo是如何获取系统配置的:

<?php
/*默认语言*/
$met_index_type = $db->get_one("SELECT * FROM $met_config WHERE name='met_index_type' and lang='metinfo'");
$met_index_type = $met_index_type['value'];
$lang=($lang=="")?$met_index_type:$lang;
$langoks = $db->get_one("SELECT * FROM $met_lang WHERE lang='$lang'");
if(!$langoks)die('No data in the database,please reinstall.');
if(!$langoks[useok]&&!$metinfoadminok)okinfo('../404.html');
if(count($met_langok)==1)$lang=$met_index_type;
/*读配置数据*/
$_M[config][tablepre]=$tablepre;
$query = "SELECT * FROM $met_config WHERE lang='$lang' or lang='metinfo'";
$result = $db->query($query);
while($list_config= $db->fetch_array($result)){
    $_M[config][$list_config['name']]=$list_config['value'];
    if($metinfoadminok)$list_config['value']=str_replace('"', '&#34;', str_replace("'", '&#39;',$list_config['value']));
    $settings_arr[]=$list_config;
    if($list_config['columnid']){
        $settings[$list_config['name'].'_'.$list_config['columnid']]=$list_config['value'];
    }else{
        $settings[$list_config['name']]=$list_config['value'];
    }
    if($list_config['flashid']){
        $list_config['value']=explode('|',$list_config['value']);
        $falshval['type']=$list_config['value'][0];
        $falshval['x']=$list_config['value'][1];
        $falshval['y']=$list_config['value'][2];
        $falshval['imgtype']=$list_config['value'][3];
        $list_config['mobile_value']=explode('|',$list_config['mobile_value']);
        $falshval['wap_type']=$list_config['mobile_value'][0];
        $falshval['wap_y']=$list_config['mobile_value'][1];
        $met_flasharray[$list_config['flashid']]=$falshval;
    }
}
$_M[lang]=$lang;
@extract($settings);

可见,这里执行了这条SQL语句 SELECT * FROM $met_config WHERE lang='$lang' or lang='metinfo' ,然后将结果extract到上下文中。

$met_config 这个表,格式就是 utf8_general_ci ,大小写不敏感。

所以,我只需要传入 index.php?lang=Cn ,在执行上述SQL语句的时候,不影响SQL语句的执行结果;而在进行 if($lang==$met_index_type)... 比较的时候, Cn != cn ,成功进入else语句。

最后,构造下面数据包,注入获取结果:
15021076542887.jpg

0x05. 漏洞利用条件

主要条件就是,需要管理员开启伪静态:

15021077601790.jpg

没有什么其他条件了,无需登录即可触发。

 
作者:离别歌
phithon的小站,长期存在与分享关于网络安全与各种编程的原创文章。
原文地址:Metinfo 5.3.17 前台SQL注入漏洞分析, 感谢原作者分享。

发表评论