一、概要
本文詳細介紹了如何通過路徑遍歷及本地文件包含(LFI)漏洞,在WordPress中實現(xiàn)遠程代碼執(zhí)行(RCE)。該漏洞在WordPress中已存在6年之久。攻擊視頻參考此處鏈接。
在WordPress站點上,如果攻擊者具備author
及以上權限,就可以在底層服務器上執(zhí)行任意PHP代碼,最終可以遠程完全接管整個站點。我們已經向WordPress安全團隊反饋了另一個漏洞,該漏洞也能讓攻擊者在任意WordPress站點上獲得類似訪問權限,目前后一個漏洞尚未修復。
二、受影響版本
在4.9.9及5.0.1版本中,由于另一個安全補丁的存在,因此本文介紹的漏洞無法順利利用。然而路徑遍歷漏洞依然可能存在,并且當前處于未修復狀態(tài)。任何WordPress站點如果安裝了無法正確處理Post Meta
條目的插件,漏洞就可能利用成功。在我們的WordPress安全月活動中,我們已經發(fā)現(xiàn)了某些流行插件(活躍安裝量達數百萬計)存在這類問題。
根據WordPress下載頁面的統(tǒng)計數據,互聯(lián)網上超過33%的站點正在使用WordPress??紤]到插件可能會帶來新的問題,并且有些網站并沒有及時更新,因此我們認為受影響的站點數仍達數百萬個。
三、技術分析
我們使用自己研發(fā)的SAST解決方案RIPS(參考示例),在3分鐘內就檢測到了路徑遍歷及本地文件包含漏洞。然而,初步分析時這些漏洞似乎無法使用。經過詳細調研,事實證明這些漏洞利用起來雖然非常復雜,但的確有可能成功利用。
攻擊過程及原理示意請參考此處視頻。
當我們將圖像上傳到WordPress站點時,圖像首先會被存放到上傳目錄中(wp-content/uploads
)。WordPress也會在數據庫中創(chuàng)建該圖像的一個內部引用,以跟蹤圖像的元信息(如圖像所有者或上傳時間)。
這種元信息以Post Meta
條目形式存放在數據庫中,每個條目都包含一對key/value,與某個特定的ID相對應。以evil.jpg
這張上傳圖像為例,相關Post Meta
如下所示:
MariaDB [wordpress]> SELECT * FROM wp_postmeta WHERE post_ID = 50;
+---------+-------------------------+----------------------------+
| post_id | meta_key | meta_value |
+---------+-------------------------+----------------------------+
| 50 | _wp_attached_file | evil.jpg |
| 50 | _wp_attachment_metadata | a:5:{s:5:"width";i:450 ... |
...
+---------+-------------------------+----------------------------+
在本例中,圖片所對應的post_ID
值為50。如果用戶后續(xù)想使用該ID
來使用或者編輯該圖像,WordPress會查找匹配的_wp_attached_file
元數據條目,使用其對應的值在wp-content/uploads
目錄中定位該文件。
在WordPress 4.9.9和5.0.1之前的版本中,Post Meta
條目可以被修改,被設置為任意值。
當某張圖像被更新時(如圖像描述發(fā)生改動),那么WordPress就會調用edit_post()
函數,該函數直接作用于$_POST
數組。
function edit_post( $post_data = null ) {
if ( empty($postarr) )
$postarr = &$_POST;
?
if ( ! empty( $postarr['meta_input'] ) ) {
foreach ( $postarr['meta_input'] as $field => $value ) {
update_post_meta( $post_ID, $field, $value );
}
}
如上所示,攻擊者有可能注入任意Post Meta
條目。由于WordPress并沒有檢查哪些條目被修改過,因此攻擊者可以更新_wp_attached_file
元數據,將其設置為任意值。該操作并不會重命名文件,只是修改了WordPress在嘗試編輯目標圖像時所要尋找的文件。這將導致路徑遍歷問題,后面我們會進一步分析。
路徑遍歷問題存在于wp_crop_image()
函數中,當用戶裁剪圖像時,該函數就會被調用。
該函數會獲取待裁剪的圖像ID值($attachment_id
),并從數據庫中獲取相應的_wp_attached_file
?Post Meta
信息。
需要注意的是,由于edit_post()
中存在缺陷,因此$src_file
可以被設置為任意值。
簡化版的wp_crop_image()
函數如下,實際代碼位于wp-admin/includes/image.php
文件中。
function wp_crop_image( $attachment_id, $src_x, ...) {
$src_file = $file = get_post_meta( $attachment_id, '_wp_attached_file' );
?
在下一步中,WordPress必須確保圖像實際存在并加載該圖像。在WordPress中加載指定圖像有兩種方法。第一種是簡單地在wp-content/uploads
目錄中,利用_wp_attached_file
?Post Meta
信息查找指定的文件名(參考下一個代碼段的第二行)。
如果該方法查找失敗,則WordPress會嘗試從站點服務器上下載該圖像,這是一種備用方案。為了完成該操作,WordPress會生成一個下載URL,該URL中包含wp-content/uploads
目錄對應的URL以及_wp_attached_file
?Post Meta
條目中存儲的文件名(如下代碼片段第6行)。
舉一個具體例子:如果_wp_attached_file
?Post Meta
條目中存儲的值為evil.jpg
,那么WordPress首先會嘗試檢查wp-content/uploads/evil.jpg
文件是否存在。如果該文件不存在,則嘗試從https://targetserver.com/wp-content/uploads/evil.jpg
這個URL下載該文件。
之所以嘗試下載文件,而不是在本地搜索文件,原因在于某些插件會在用戶訪問URL時動態(tài)生成圖像。
請注意,這個過程并沒有進行任何過濾處理。WordPress只會簡單地將上傳目錄以及URL拼接起來(URL中包含用戶輸入的$src_file
)。
一旦WordPress通過wp_get_image_editor()
成功加載一個有效的圖像,就會進行裁剪處理。
?
if ( ! file_exists( "wp-content/uploads/" . $src_file ) ) {
// If the file doesn't exist, attempt a URL fopen on the src link.
// This can occur with certain file replication plugins.
$uploads = wp_get_upload_dir();
$src = $uploads['baseurl'] . "/" . $src_file;
} else {
$src = "wp-content/uploads/" . $src_file;
}
$editor = wp_get_image_editor( $src );
?
經過裁剪的圖片隨后會被保存回文件系統(tǒng)中(無論是下載文件還是本地文件)。保存文件所使用的文件名為get_post_meta()
所返回的$src_file
,而攻擊者可以控制這個值。代碼中對文件名做的唯一一處修改是為文件的basename(去掉文件名的目錄及后綴)添加cropped-
前綴字符串(如下代碼段中第4行)。以前面的evil.jpg
為例,這里生成的結果文件名為cropped-evil.jpg
。
如果結果文件路徑不存在,則WordPress隨后會使用wp_mkdir_p()
創(chuàng)建相應的目錄(參考第6行代碼)。
隨后,WordPress使用圖像編輯器對象的save()
方法,將圖像最終寫入文件系統(tǒng)中。save()
方法也沒有對給定的文件名執(zhí)行目錄遍歷檢查。
?
$src = $editor->crop( $src_x, $src_y, $src_w, $src_h, $dst_w, $dst_h, $src_abs );
$dst_file = str_replace( basename( $src_file ), 'cropped-' . basename( $src_file ), $src_file );
wp_mkdir_p( dirname( $dst_file ) );
$result = $editor->save( $dst_file );
到目前為止,我們已經分析了哪個文件可能會被載入圖像編輯器中,因為WordPress沒有執(zhí)行過濾操作。然而,如果該文件并不是有效的圖像,那么圖像編輯器就會拋出異常。因此這里第一個假設是,WordPress只能裁剪上傳目錄外的圖像。
然而,由于WordPress在沒找到圖像時會嘗試下載圖像,因此會導致遠程代碼執(zhí)行(RCE)漏洞。
本地文件 | HTTP下載文件 | |
---|---|---|
上傳的文件 | evil.jpg | evil.jpg |
_wp_attached_file | evil.jpg?shell.php | evil.jpg?shell.php |
待加載的結果文件 | wp-content/uploads/evil.jpg?shell.php | https://targetserver.com/wp-content/uploads/evil.jpg?shell.php |
實際位置 | wp-content/uploads/evil.jpg | https://targetserver.com/wp-content/uploads/evil.jpg |
結果文件名 | None – 文件加載失敗 | evil.jpg?cropped-shell.php |
我們可以將_wp_attached_file
的值設置為evil.jpg?shell.php
,這樣WordPress就會發(fā)起一個HTTP請求,請求URL為https://targetserver.com/wp-content/uploads/evil.jpg?shell.php
。由于在該上下文中,?
后的所有字符都會被忽略,因此該請求會返回一個有效的圖像文件。最終結果文件名會變成evil.jpg?shell.php
。
然而,雖然圖像編輯器的save()
方法沒有檢查路徑遍歷攻擊,但會將待加載的圖像的mime
擴展名附加到結果文件名中。在本例中,生成的文件名將為evil.jpg?cropped-shell.php.jpg
。這樣可以讓新創(chuàng)建的文件再次保持無害狀態(tài)。
然而,我們還是可以使用類似evil.jpg?/../../evil.jpg
的載荷,將結果圖像植入任意目錄中。
每個WordPress主題實際上都是位于wp-content/themes
目錄中的一個子目錄,可以為不同場景提供模板文件。比如,如果博客的某位訪問者想查看博客文章,WordPress則會在當前激活的主題中查找post.php
文件。如果找到模板,則會include()
該模板。
為了支持額外的自定義層,我們可以為某些文章選擇自定義模板。為了完成該任務,用戶需要設置數據庫中的_wp_page_template
?Post Meta
條目,將其設置為自定義文件名。這里唯一的限制條件是:待include()
的文件必須位于當前激活的主題目錄中。
通常情況下,該目錄無法訪問,并且不會有文件上傳到該目錄中。然而,攻擊者可以濫用前文描述的路徑遍歷漏洞,將惡意構造的圖像植入當前使用的主題目錄中。隨后攻擊者可以創(chuàng)建一個新的帖子,濫用同一個bug,更新_wp_attached_file
Post Meta
數據庫條目,以便include()
該圖像。將PHP代碼注入圖像后,攻擊者隨后就能獲得任意遠程代碼執(zhí)行權限。
WordPress支持PHP的兩種圖像編輯擴展:GD以及Imagick。這兩者有所不同,Imagick并不會刪除圖像的exif
元數據,這樣我們就可以將PHP代碼藏身其中。GD會壓縮每張圖像,刪除所有的exif
元數據。
然而,我們還是可以制作包含精心構造的像素的圖像來利用漏洞,當GD裁剪完圖像后,這些像素會以某種方式進行反轉,最終達到PHP代碼執(zhí)行執(zhí)行目標。在我們研究PHP GD擴展的內部結構過程中,libgd
又爆出可被利用的一個內存破壞漏洞(CVE-2019-6977)。
四、時間線
日期 | 事件 |
---|---|
2018/10/16 | 我們在Hackerone上將漏洞反饋給WordPress |
2018/10/18 | 某個WordPress安全團隊成員確認該報告,并表示在驗證報告后會回頭聯(lián)系我們 |
2018/10/19 | 另一個WordPress安全團隊成員請求了解更多信息 |
2018/10/22 | 我們向WordPress提供了更多信息,并提供了包含270行利用代碼的完整腳本,幫助對方確認漏洞 |
2018/11/15 | WordPress觸發(fā)該漏洞,表示可以復現(xiàn)該漏洞 |
2018/12/06 | WordPress 5.0發(fā)布,沒有修復該漏洞 |
2018/12/12 | WordPress 5.0.1發(fā)布,包含安全更新。某個補丁會阻止攻擊者任意設置post meta條目,因此使該漏洞無法直接利用。然而,路徑遍歷漏洞依然存在,并且如果已安裝的插件沒有正確處理Post Meta條目就可以利用該漏洞。WordPress 5.0.1并沒有解決路徑遍歷或者本地文件包含漏洞 |
2018/12/19 | WordPress 5.0.2發(fā)布,沒有修復漏洞 |
2019/01/09 | WordPress 5.0.3發(fā)布,沒有修復漏洞 |
2019/01/28 | 我們詢問WordPress下一個安全版本的發(fā)布時間,以便協(xié)調我們的文章公布時間,準備在補丁發(fā)布后公布我們的分析文章 |
2019/02/14 | WordPress推出補丁 |
2019/02/14 | 我們提供補丁反饋,驗證補丁的確能緩解漏洞利用過程 |
五、總結
本文介紹了WordPress中存在的一個遠程代碼執(zhí)行漏洞,該漏洞存在時間已超過6年。RIPS報告了5.0.1版以及4.9.9版中的另一個漏洞,打上該漏洞補丁后,這個RCE漏洞也無法正常利用。然而如果目標站點安裝了允許覆蓋任意Post Data的插件,那么依然可以利用路徑遍歷漏洞。由于我們在攻擊目標WordPress站點時需要通過身份認證,因此我們決定在報告漏洞4個月后再公開該漏洞。
感謝WordPress安全團隊的志愿者們,他們在該問題溝通上非常友好并且非常專業(yè)。