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