作者:
RickGray
·
2015/09/21 14:01
By: RickGray (知道創宇404安全實驗室)
近日,WordPress 釋出了新版本4.3.1,其中修復了幾個嚴重的安全問題,其中包含了由 Check Point 所提交的一個跨站指令碼漏洞(CVE-2015-5714)和一個許可權提升漏洞(CVE-2015-5715)。
8月初,Check Point 在其官方部落格上發表了一篇關於 《WordPress漏洞三部曲》 系列文章的第一部,在這篇文章中,提及了 WordPress 在 4.2.3 版本中修復的一個越權漏洞,這裡對此就不再做具體分析和說明,相關細節詳情可參考原文和 phithon 所寫的 《Wordpress4.2.3提權與SQL隱碼攻擊漏洞(CVE-2015-5623)分析》。
這裡主要說明的是 "三部曲" 中的第三部,也就是 Check Point 在其部落格上公開的關於 WordPress 4.3.1 版本中所修復的另一個越權漏洞和一個跨站指令碼漏洞(原文在這裡)。
0x01 "KSES"與簡碼過濾差異化導致的 XSS
首先來看看跨站指令碼漏洞。WordPress 在編輯文章內容時允許使用簡碼(shorcodes)來表示資源(圖片,連結等)。WordPress 中開啟了白名單機制去過濾 HTML 標籤,只有在白名單規則裡的標籤,才允許被使用,並且會使用專用指令碼 "KSES" 去檢測和過濾這些 HTML 標籤。這裡需要說明的是,WordPress 對 HTML 標籤的檢測和過濾發生在將內容插入資料庫時,而簡碼的解析渲染髮生在將內容輸出到頁面時,下面簡單用例子說明一下兩個處理過程的差別,編輯文章插入內容為:
#!html
TEST!!![caption width="1" caption='<a href="' ">]</a><a>xxxxxx</a>
因插入的內容包含完整且符合白名單規則的 HTML 標籤,而簡碼 [caption]
(caption簡碼說明) 並不包含在 "KSES" 檢測的內容裡,最後輸出內容到前臺時簡碼解析後會被渲染為:
#!html
<p>TEST!!!<figure style="width: 1px;" class="wp-caption alignnone"><figcaption class="wp-caption-text"><a href="</figcaption></figure></a><a>xxxxxx</a></p>
由於在 "KSES" 過濾檢測時只關 HTML 標籤,對簡碼並不進行檢測,又因簡碼中屬性都以 KEY=VALUE
的形式出現,用單引號'
或者雙引號"
包裹值Value
,因此在 TEST!!![caption width="1" caption='<a href="' ">]</a><a>xxxxxx</a>
這段內容中,簡碼 caption
有兩個屬性,分別為:
width: 1
caption: <a href="
而後半部分的 <a href="' ">]</a><a>xxxxxx</a>
又為正常的 HTML 標籤閉合形式,因此並不會被 "KSES" 檢測過濾後丟棄掉。最終在前臺輸出時,簡碼 caption
被解析,使得最後出現 <a>
標籤中 href
屬性值未閉合的情況。
因此利用前後處理的差異,可以構造出有利的 payload 形成 XSS:
#! html
TEST!!![caption width="1" caption='<a href="' ">]</a><a href="http://onMouseOver='alert(1)'">Click me</a>
將上面 payload 作為文章內容釋出,前端渲染出來的結果為:
#!html
TEST!!!<figure style="width: 1px;" class="wp-caption alignnone"><figcaption class="wp-caption-text"><a href="</figcaption></figure></a><a href="http://onMouseOver='alert(1)'">Click me</a></p>
輸出的內容在瀏覽器中解析成 <a>
標籤部分,href
屬性值為 </figcaption></figure></a><a href=
,而 http://
由於雙斜槓(//)的原因與 onMouseOver='alert(1)
部分斷開,因此一個 onmouseover 屬性被解析出來,形成 XSS。
該漏洞(CVE-2015-5714)已經在 WordPress 新版 4.3.1 中修復,具體 patch 部分位於兩處,第一處在 wp-includes/shortcodes.php
中的 shortcode_parse_atts() 函式中:
--- wp-includes/shortcodes.php
+++ wp-includes/shortcodes.php
@@ -462,6 +462,15 @@
elseif (isset($m[8]))
$atts[] = stripcslashes($m[8]);
}
+
+ // Reject any unclosed HTML elements
+ foreach( $atts as &$value ) {
+ if ( false !== strpos( $value, '<' ) ) {
+ if ( 1 !== preg_match( '/^[^<]*+(?:<[^>]*+>[^<]*+)*+$/', $value ) ) {
+ $value = '';
+ }
+ }
+ }
} else {
$atts = ltrim($text);
}
新新增的處理過程,過濾了在簡碼屬性值中出現的未閉合 HTML 標籤的值。並且解析簡碼時使用 wp_kses() 函式進行了過濾,確保輸出的內容被編碼(程式碼位於 wp-includes/media.php
):
--- wp-includes/media.php
+++ wp-includes/media.php
@@ -863,6 +863,8 @@
$content = $matches[1];
$attr['caption'] = trim( $matches[2] );
}
+ } elseif ( strpos( $attr['caption'], '<' ) !== false ) {
+ $attr['caption'] = wp_kses( $attr['caption'], 'post' );
}
/**
這樣一來就很難利用上面所說的 "KSES"和簡碼解析前後處理差異 成功構造出能夠進行 XSS 的 HTML 標籤了。
0x02 許可權檢查遺漏導致越權操作
Check Point 在文章中還提到了另一個越權操作(與 part1 的越權不同),可以使得不具有文章釋出許可權的使用者透過 XMLRPC 操作將自己的文章狀態修改為 private
,並可將其置頂 (WordPress 4.3.0版本中已將其修復,未設密碼的私有文章不可置頂)。
越權操作位於 XMLRPC 文章編輯操作中,涉及檔案 /wp-includes/class-wp-xmlrpc-server.php
(5042-5327) 其中關鍵程式碼分析:
#!php
public function mw_editPost( $args ) {
$this->escape( $args );
$post_ID = (int) $args[0]; // 獲取需要編輯的文章ID (使用者所屬)
$username = $args[1]; // 從請求的xml中獲取使用者名稱
$password = $args[2]; // 從請求的xml中獲取使用者密碼
$content_struct = $args[3]; // 從請求的xml中獲取結構
$publish = isset( $args[4] ) ? $args[4] : 0;
(...省略)
if ( isset( $content_struct["{$post_type}_status"] ) ) {
switch( $content_struct["{$post_type}_status"] ) {
case 'draft':
case 'pending':
case 'private':
case 'publish':
$post_status = $content_struct["{$post_type}_status"]; // 資料庫中儲存的文章型別為post,所以取的是xml中 post_status 引數的值
break;
default:
$post_status = $publish ? 'publish' : 'draft';
break;
}
}
首先處理程式獲取提交引數並驗證當前使用者許可權,對於草稿或者未稽核的文章,其資料庫中儲存的文章型別為 post
,所以在取值 $content_struct["{$post_type}_status"]
時,獲取的是提交引數中 post_status
的值。
#!php
(...省略)
// 當使用者不具有文章釋出許可權時,`publish` 操作會被禁止
// 但是這裡並沒有限制 `private` 的情況
// 所以若xml中 post_status 引數值為 private 則跳過檢查
if ( ('publish' == $post_status) ) {
if ( ( 'page' == $post_type ) && ! current_user_can( 'publish_pages' ) ) {
return new IXR_Error( 401, __( 'Sorry, you do not have the right to publish this page.' ) );
} elseif ( ! current_user_can( 'publish_posts' ) ) {
return new IXR_Error( 401, __( 'Sorry, you do not have the right to publish this post.' ) );
}
}
接著,程式會驗證其提交的需要更新的文章狀態。當使用者透過 XMLRPC 進行文章編輯時,若想釋出一篇未釋出的文章時,會檢查使用者是否具有文章釋出的許可權。但是該檢查判斷了將文章狀態變為釋出狀態的情況下(post_status == publish),而針對將文章狀態變為私有狀態的情況程式碼中並沒有進行檢查。程式上的判斷疏忽,致使我們可以使用一個不具有文章釋出許可權的帳號將自己一篇 未透過稽核
或者 存於垃圾箱
的文章的轉檯透過該過程將其改為私有(private
),讓該文章以私文的形式在前臺顯示出來,管理員以及其他具有許可權的使用者都能瀏覽到。
另一個需要說明的點就是,透過 XMLRPC 操作編輯文章時,可以將文章進行置頂,具體程式碼為:
#!php
(...省略)
// 將文章置頂(4.3.0 版本後不能將未設密碼的私有文章置頂)
// Only posts can be sticky
if ( $post_type == 'post' && isset( $content_struct['sticky'] ) ) {
$data = $newpost;
$data['sticky'] = $content_struct['sticky'];
$data['post_type'] = 'post';
$error = $this->_toggle_sticky( $data, true );
if ( $error ) {
return $error;
}
}
但是該問題在 WordPress 4.3.0 版本中已經得到了限制:
#!php
private function _toggle_sticky( $post_data, $update = false ) {
$post_type = get_post_type_object( $post_data['post_type'] );
// Private and password-protected posts cannot be stickied.
if ( 'private' === $post_data['post_status'] || ! empty( $post_data['post_password'] ) ) {
// 如果需要置頂的文章為私有狀態,並且未設訪問密碼,不能將其置頂,並自動取消之前的置頂狀態
// Error if the client tried to stick the post, otherwise, silently unstick.
if ( ! empty( $post_data['sticky'] ) ) {
return new IXR_Error( 401, __( 'Sorry, you cannot stick a private post.' ) );
}
if ( $update ) {
unstick_post( $post_data['ID'] );
}
} elseif ( isset( $post_data['sticky'] ) ) {
// 如果需要置頂的文章為私有狀態,並且設有訪問密碼,且具有編輯其他文章的許可權,則將其所置頂的文章置頂
if ( ! current_user_can( $post_type->cap->edit_others_posts ) ) {
return new IXR_Error( 401, __( 'Sorry, you are not allowed to stick this post.' ) );
}
$sticky = wp_validate_boolean( $post_data['sticky'] );
if ( $sticky ) {
stick_post( $post_data['ID'] );
} else {
unstick_post( $post_data['ID'] );
}
}
}
未設密碼訪問的私有文章已經無法再透過 XMLRPC 編輯文章操作將文章置頂。
下面透過示例來說明如何透過 XMLRPC 編輯文章操作將文章狀態修改為 私有
。為了方便演示,這裡事先註冊好一個使用者(投稿者許可權,其投稿的文章狀態為 pending
),並提交一篇文章投遞申請:
檢視一下待審文章在資料庫中的狀態:
然後根據上面所分析的許可權提升細節,構造 payload ,將此待審文章狀態更改為 private
:
可以看到返回訊息中提示置頂私有文章失敗,這是因為測試時使用的 WordPress 4.3.0 版本,該版本中已經修復了私有文章任意置頂的問題。
然後檢視一下透過 XMLRPC 編輯文章後資料庫中待稽核文章的狀態:
資料庫中文章狀態已經變為私有,再到前臺檢視首頁:
由投稿使用者提交的待稽核文章已經變為私有狀態顯示在前臺頁面中,並且管理員能看到所有的私有文章。
0x03 結合跨站指令碼和越權操作
本文一開時已經分析過了如何透過利用 "KSES"與簡碼過濾差異化造成一個儲存型的前臺 XSS,加上第二節所演示越權編輯文章狀態的過程,結合這兩個漏洞,可以使得站點上具有一點許可權的使用者(投稿即可),投遞包含惡意內容的文章,然後利用越權操作將文章顯示到前臺,對能夠瀏覽到該文章的使用者(包括管理員)進行 XSS 攻擊。
下面我們模擬一下上面敘述的流程。首先投遞一篇包含 XSS payload 的文章,利用 "KSES"與簡碼渲染操作的差異使得內容在前臺渲染後能夠形成 XSS,將文章內容設定為:
XSS LOL!!![caption width='1' caption='<a href="' ">]</a><a href="http://onMouseOver='alert(/xss/)' style='display:block;position:absolute;top:0px;left:0px;margin-left:-1000px;margin-top:-1000px;width:99999px;height:99999px;'"></a>
然後利用 XMLRPC 遍歷文章得到提交的待稽核文章的 id,這裡得到待稽核文章 id 為:28
,在構造 payload 將其未釋出狀態改為私有:
利用 XMLRPC 文章編輯成功修改文章狀態為私有後,訪問前臺檢視結果:
0x04 三部曲之歌
回顧 Check Point 所釋出的《WordPress漏洞三部曲》,可以知道 WordPress 在 4.2.2 版本中含有其提交的所有漏洞,包括了 競爭條件下許可權提升
,文章恢復導致SQL隱碼攻擊
,"KSES"與簡碼過濾差異化導致的XSS
,許可權檢查遺漏導致越權操作
等。通看起來,如果在 WordPress 4.2.2 版本下,這些漏洞都能在以 競爭條件下許可權提升
作為起始,完成後面的攻擊,實現一個超低許可權使用者下進行 SQL 注入、XSS 攻擊的操作。我將 Check Point 在 part1 和 part3 中所提到的漏洞利用方法綜合到一起,寫出了 all in one
的 PoC,其中 競爭條件下許可權提升
的過程使用 phithon 文章中所提及的使用兩個訂閱使用者來解決 7 天攻擊週期的限制。
為了達到 all in one
的演示結果,將 WordPress 測試環境更換為 4.2.2 版本,並事先準備兩個訂閱使用者 guest:guest888
,test:test888
,然後執行 PoC:
PoC 提示成功後,管理員訪問前臺,文章成功置頂幷包含惡意程式碼:
0x05 總結
這裡不得不佩服洞主對 WordPress 熟悉程度和漏洞挖掘的思路。
雖然 WordPress 在幾個連續的版本中修復了這些漏洞,但在非最新版本中(< 4.3.1)中,這些漏洞還是能夠在特定場景下得以利用。尤其是在 4.2.2 版本中,能夠利用 "三部曲" 中所提及的漏洞進行一系列的攻擊操作。
這些看似雞肋的漏洞在我看來並不雞肋,雞肋只是因為還未找到合適的應用場景而已。
0x06 參考連結
原文出處:http://blog.knownsec.com/2015/09/wordpress-vulnerability-analysis-cve-2015-5714-cve-2015-5715/
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!