前言
Ambionics團(tuán)隊(duì)在審計(jì)Drupal的服務(wù)模塊后發(fā)現(xiàn)unserialize()
的不安全使用造成了嚴(yán)重的漏洞。這個(gè)漏洞允許特權(quán)升級(jí),SQL注入,最后造成遠(yuǎn)程代碼執(zhí)行。
服務(wù)模塊
Drupal的服務(wù)模塊是一種構(gòu)建API的標(biāo)準(zhǔn)化解決方案,以便外部客戶端可以與Drupal通信。它允許任何人構(gòu)建SOAP,REST或XMLRPC,以便以多種輸出格式發(fā)送和獲取信息。它目前是Drupal的150個(gè)最常用插件之一,大約45000正在使用。
服務(wù)允許使用不同的資源創(chuàng)建不同的端點(diǎn),允許以面向API的方式與網(wǎng)站及其內(nèi)容進(jìn)行交互。例如,可以啟用/user/login
資源通過JSON或XML登錄。
返回
漏洞
模塊的一個(gè)特點(diǎn)是可以通過更改Content-Type
/Acceptheaders
來控制輸入/輸出格式。默認(rèn)情況下允許以下輸入格式:
- application/xml
- application/json
- multipart/form-data
- application/vnd.php.serialized
最后一個(gè)是給PHP序列化的數(shù)據(jù)類型。讓我們?cè)僭囈淮危?/p>
返回:
因此確實(shí)存在一個(gè)unserialize()
使用不當(dāng)?shù)穆┒础?/p>
<?php
function rest_server_request_parsers () {
static $ parsers = NULL ;
if (!$ parsers ) {
$ parsers = array (
'application / x-www-form-urlencoded' => 'ServicesParserURLEncoded' ,
'application / json' => 'ServicesParserJSON' ,
'application / vnd.php.serialized' = > 'ServicesParserPHP' ,
'multipart / form-data' => 'ServicesParserMultipart' ,
'application / xml' => 'ServicesParserXML' ,
'text / xml' => 'ServicesParserXML' ,
);
}
}
class ServicesParserPHP implements ServicesParserInterface {
public function parse (ServicesContextInterface $ context ) {
return unserialize ($ context - > getRequestBody ());
}
} } } class ServicesParserPHP implements ServicesParserInterface { public function parse
(ServicesContextInterface $ context ){ return unserialize ($ context - > getRequestBody ()); } } } }
class ServicesParserPHP implements ServicesParserInterface { public function parse
(ServicesContextInterface $ context ){ return unserialize ($ context - > getRequestBody ()); } }
我們能用它做什么?
利用
即使Drupal沒有unserialize()
小工具,服務(wù)中可用的許多端點(diǎn),以及發(fā)送序列化數(shù)據(jù)的能力,提供了很多方法來利用此漏洞:用戶提交的數(shù)據(jù)可以在SQL查詢中使用,回顯在結(jié)果等。開發(fā)關(guān)注/user/login
,因?yàn)樗强蛻糁凶畛S玫慕K端。盡管如此,只要PHP反序列化被激活,仍然可以構(gòu)造任何URL上的RCE?Payload。
SQL注入
顯然,/user/login
端點(diǎn)的主要功能是允許人們進(jìn)行身份驗(yàn)證。為此,服務(wù)使用通常的Drupal內(nèi)部API,它從數(shù)據(jù)庫中提取用戶名,然后將密碼哈希值與用戶提交的密碼進(jìn)行比較。這意味著我們發(fā)送的用戶名將使用Drupal數(shù)據(jù)庫API的SQL查詢。調(diào)用就像這樣:
<?php $user = db_select('users', 'base') # Table: users Alias: base ->fields('base', array('uid', 'name', ...)) # Select every field ->condition('base.name', $username) # Match the username ->execute(); # Build and run the query
像unserialize()
的bug一樣,框架的漏洞來自于自身的功能。實(shí)際上不像我們通常那樣提交像字符串這樣的基本類型,API提供了通過SelectQueryInterface
給它一個(gè)實(shí)現(xiàn)Drupal的對(duì)象來進(jìn)行子查詢的可能性。
<?php class DatabaseCondition implements QueryConditionInterface, Countable { public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) { if ($condition['value'] instanceof SelectQueryInterface) { $condition['value']->compile($connection, $queryPlaceholder); $placeholders[] = (string) $condition['value']; $arguments += $condition['value']->arguments(); // Subqueries are the actual value of the operator, we don't // need to add another below. $operator['use_value'] = FALSE; } } }
對(duì)象的字符串表示直接用于查詢,這可能會(huì)引起SQL注入。
以下情況需要不同的條件$username
:
- 它必須實(shí)現(xiàn)
SelectQueryInterface
- 它必須實(shí)現(xiàn)
compile()
- 它的字符串必須由我們控制
SelectQueryExtender
是實(shí)現(xiàn)SelectQueryInterface
的其中兩個(gè)對(duì)象之一,意在包圍一個(gè)標(biāo)準(zhǔn)的SelectQuery
對(duì)象。它的$query
屬性包含所述對(duì)象。當(dāng)調(diào)用SelectQueryExtender的compile()
和__toString()_
方法時(shí),將調(diào)用基礎(chǔ)對(duì)象的方法。
<?php class SelectQueryExtender implements SelectQueryInterface { /** * The SelectQuery object we are extending/decorating. * * @var SelectQueryInterface */ # Note: Although this expects a SelectQueryInterface, this is never enforced protected $query; public function __toString() { return (string) $this->query; } public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) { return $this->query->compile($connection, $queryPlaceholder); } }
我們可以使用這個(gè)類作為任何其他類的“代理”:這允許我們傳遞第一個(gè)條件。
DatabaseCondition
對(duì)象滿足了兩個(gè)最后的條件:出于性能原因,它有一個(gè)stringVersion
屬性,意味著在編譯之后包含它的字符串。
<?php class DatabaseCondition implements QueryConditionInterface, Countable { protected $changed = TRUE; protected $queryPlaceholderIdentifier; public function compile(DatabaseConnection $connection, QueryPlaceholderInterface $queryPlaceholder) { // Re-compile if this condition changed or if we are compiled against a // different query placeholder object. if ($this->changed || isset($this->queryPlaceholderIdentifier) && ($this->queryPlaceholderIdentifier != $queryPlaceholder->uniqueIdentifier())) { $this->changed = FALSE; $this->stringVersion = implode($conjunction, $condition_fragments); } } public function __toString() { // If the caller forgot to call compile() first, refuse to run. if ($this->changed) { return NULL; } return $this->stringVersion; } }
這里SQL注入最有效的利用方式是使用UNION
提取管理員,并用我們的密碼替換他的密碼哈希。
# Original Query SELECT ..., base.name AS name, base.pass AS pass, base.mail AS mail, ... FROM {users} WHERE (name = # Injection starts here 0x3a) UNION SELECT ..., base.name AS name, '$S$DfX8LqsscnDutk1tdqSXgbBTqAkxjKMSWIfCa7jOOvutmnXKUMp0' AS pass, base.mail AS mail, ... FROM {users} ORDER BY (uid # Injection ends here );
我們還可以在其他字段中存儲(chǔ)其他數(shù)據(jù)庫數(shù)據(jù),例如將管理員的原始hash放在他的簽名中。
現(xiàn)在我們可以以管理員身份登錄從數(shù)據(jù)庫中讀取任何內(nèi)容。
遠(yuǎn)程代碼執(zhí)行
Drupal有一個(gè)緩存表,將要序列化的數(shù)據(jù)相關(guān)聯(lián)。Services模塊為每個(gè)端點(diǎn)緩存資源列表及其期望的參數(shù)以及與其相關(guān)聯(lián)的回調(diào)函數(shù)。修改緩存會(huì)產(chǎn)生巨大的影響,因?yàn)槲覀兛梢阅K調(diào)用任何PHP函數(shù),任何參數(shù)。而且DrupalCacheArray
類允許我們這樣做:
- 修改
/user/login
資源的行為以在服務(wù)器上的任何位置寫入文件 - 點(diǎn)擊
/user/login
創(chuàng)建文件 - 恢復(fù)標(biāo)準(zhǔn)行為
為了在攻擊期間不中斷端點(diǎn),我們使用SQL注入來獲取原始緩存數(shù)據(jù),以便我們只修改特定值。我們使用file_put_contents()
和兩個(gè)參數(shù)在任何地方寫一個(gè)文件。
補(bǔ)丁
如果您使用此模塊的漏洞版本,請(qǐng)盡快更新。在您無法更新的情況下,我們強(qiáng)烈建議在Drupal Services
設(shè)置中禁用application/vnd.php.serialized
。
Exploit
以下exploit結(jié)合了這兩個(gè)漏洞的利用,執(zhí)行特權(quán)升級(jí),SQL注入和RCE。
https://github.com/mottoin/Drupal-Exploit
轉(zhuǎn)載來源:MottoIN
原文地址:http://www.mottoin.com/98140.html