分享 | 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