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{...
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];`:获取`$dir`s的倒数第二个元素作为`$dir_dirnamr`变量 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`、`class`3不能有值。这个条件,只要我访问的是`index.php`,并且不主动传入这三个参数,即可满足。 5. `$index`不能等于`'index'`,这个变量也是可控的,传入参数`index=xxxx`即可 5. `$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的字符格式,一般来说分为这两类: ``` <character set>_<language/other>_<ci/cs> <character set>_bin ``` 比如,最常用的`utf8_general_ci`,就是第一种格式。 我们这里需要关注的就是最后一串:ci、cs、bin,这三个究竟是什么? ci 其实就是 case insensitive (大小写不敏感)的缩写, cs 是 case sensitive (大小写敏感)的缩写。也就是说,当我们用的字符格式是`utf8_general_ci`时,Mysql中比对字符串的时候是大小写不敏感的。 bin指的是比较的时候,按照二进制的方式比较,这种情况下就不存在大小写的问题了。bin方式还可以解决有些小语种上的特性,这个就不展开说了。 我们随便找了个数据表,做个小实验:  可见上图,虽然我查询的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('"', '"', str_replace("'", ''',$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语句。 最后,构造下面数据包,注入获取结果:  ### 0x05. 漏洞利用条件 主要条件就是,需要管理员开启伪静态:  没有什么其他条件了,无需登录即可触发。