WordPress 5.1 CSRF to RCE 漏洞詳解

WordPress是使用PHP語言開發的博客平臺,用戶可以在支持PHP和MySQL數據庫的服務器上架設屬於自己的網站。也可以把 WordPress當作一個內容管理系統(CMS)來使用。前些日子,RIPS放了一個WordPress5.1的CSRF漏洞通過本文將對此次CSRF漏洞進行詳細分析,RCE相關的分析見後續分析文章


預備知識


在wordpress中,超級管理員是可以在評論中寫入任何代碼而不被過濾的


比如,在評論中輸入

WordPress 5.1 CSRF to RCE 漏洞詳解

WordPress 5.1 CSRF to RCE 漏洞詳解

直接彈框

但是超級管理員在提交評論表單時,wordpress需要校驗其Nonce值

想理解這個漏洞,首先要了解下wordpress的Nonce ( number used once )防禦機制

Wordpress的Nonce ( number used once ) 機制,是用來防止CSRF而引進的。WordPress會為一些請求提供一個隨機數進行校驗,以防止未授權的請求的發生。

來看下wordpress的Nonce機制是如何使用的:

1、使用wp_create_nonce生成 nonce值:

WordPress 5.1 CSRF to RCE 漏洞詳解

可見,其實nonce值與$i、$action、$uid、$token有關

這裡的$i 是nonce創建的時間相關變量,由wp_nonce_tick()生成,其餘的$action、$uid、$token很好理解。

由這裡我們可以看出,nonce的生成,與其操作也是有關係的

2、將生成的 nonce傳遞給需要提交時驗證的前端模板

3、需要驗證的表單被提交後,驗證其中nonce,例如下圖中,本次漏洞點

WordPress 5.1 CSRF to RCE 漏洞詳解

Nonce講解完畢,言歸正傳,分析本次漏洞


漏洞分析


理論上,如果沒法通過Nonce驗證,後續的操作會直接被終止,而且在csrf攻擊中,攻擊者是沒有辦法偽造管理員實時的Nonce值。


但從本次漏洞處來看,如下圖

WordPress 5.1 CSRF to RCE 漏洞詳解

這裡雖然沒有通過Nonce的驗證(wp_verify_nonce),但是並未終止操作。Wordpress在這裡使用了兩個過濾方法對後續的數據進行過濾。

至於為什麼沒有終止,而採用瞭如下的過濾邏輯,據說是因為WordPress其中有一些特殊的功能例如trackbacks and pingbacks會受到該值的影響,筆者沒有進一步考究,感興趣的同學可以自己分析下。

到目前為止,我們雖然沒有合法的nonce值,但我們的payload仍然倖存,

接下來,看看邏輯裡的 kses_init_filters()這個方法

WordPress 5.1 CSRF to RCE 漏洞詳解


超級管理員&非法Nonce情況:


我們用超級管理員身份提交一個評論,但是改包,把&_wp_unfiltered_html_comment改為空,使其通過不了Nonce校驗,如下圖


WordPress 5.1 CSRF to RCE 漏洞詳解

果然進入下圖斷點

WordPress 5.1 CSRF to RCE 漏洞詳解

緊接著,進入如下斷點

WordPress 5.1 CSRF to RCE 漏洞詳解

使用wp_filter_post_kses對輸入的數據進行過濾


普通用戶情況:


此時用普通用戶進行評論


WordPress 5.1 CSRF to RCE 漏洞詳解

WordPress 5.1 CSRF to RCE 漏洞詳解

直接調用wp_filter_kses進行過濾

以上思路以及明朗了

超級管理員&合法nonce ->不做任何過濾

超級管理員&不合法nonce ->wp_filter_post_kses

普通用戶 –>wp_filter_kses

先來看看普通用戶提交和超級管理員無nonce提交時調用的過濾函數有什麼區別

普通用戶提交過濾函數:

WordPress 5.1 CSRF to RCE 漏洞詳解

超級管理員無nonce提交過濾函數:

WordPress 5.1 CSRF to RCE 漏洞詳解

可以看出只是wp_kses中第二個參數不同,一個是current_filter(),一個是’post’

這裡不同的,對應wp_kses中,應該是allowed_html參數值

這裡舉個普通用戶評論的例子,普通用戶提交評論,current_filter()方法返回的值是pre_comment_content,也就是說allowed_html參數值為pre_comment_content。可見下圖動態調試結果

WordPress 5.1 CSRF to RCE 漏洞詳解

對應的,超級管理員無nonce提交時,這裡的allowed_html參數值為”post”

那麼allowed_html值不同,到底會有什麼區別呢?

$allowed_html被傳入wp_kses_split方法

WordPress 5.1 CSRF to RCE 漏洞詳解

進一步看wp_kses_split

WordPress 5.1 CSRF to RCE 漏洞詳解

注意到這裡$pass_allowed_html = $allowed_html;

現在$allowed_html傳給了$pass_allowed_html

我們要看看這兩個不同的$allowed_html最終傳遞到哪裡被用到

跟進_wp_kses_split_callback,$allowed_html傳給了wp_kses_split2

WordPress 5.1 CSRF to RCE 漏洞詳解

跟進wp_kses_split2,$allowed_html被傳給了wp_kses_attr

WordPress 5.1 CSRF to RCE 漏洞詳解

跟進wp_kses_attr,$allowed_html被傳給了wp_kses_allowed_html

WordPress 5.1 CSRF to RCE 漏洞詳解

跟進wp_kses_allowed_html

一路跟蹤,到了這裡,$allowed_html終於有作用了

WordPress 5.1 CSRF to RCE 漏洞詳解

回顧一下,

超級管理員無nonce提交時,這裡的allowed_html參數值為”post”

普通用戶提交評論時, allowed_html參數值為”pre_comment_content”。

首先看超級管理員無nonce提交嗎,allowed_html參數值為”post”,進入post分支

WordPress 5.1 CSRF to RCE 漏洞詳解

可以看到這裡有一個wp_kses_allowed_html方法,跟進去看看

WordPress 5.1 CSRF to RCE 漏洞詳解

相當於一個白名單機制,再看看白名單上都有什麼,看看$allowedposttags

WordPress 5.1 CSRF to RCE 漏洞詳解

這裡’a’標籤運行’rel’屬性

再看看普通用戶提交評論時, allowed_html參數值為”pre_comment_content”情況。

WordPress 5.1 CSRF to RCE 漏洞詳解

這裡白名單是$allowedtags

WordPress 5.1 CSRF to RCE 漏洞詳解

只允許’href’與’title’

看到這裡,明白wp_filter_post_kses 、wp_filter_ kses兩個過濾函數有什麼區別了嗎?

可以用’rel’屬性與不可以用’rel’,有什麼區別呢?如何造成這次的csrf呢?看下圖

wp-includes\\formatting.php

WordPress 5.1 CSRF to RCE 漏洞詳解

可以看到屬性值在沒有被轉義處理的情況下就再次拼接在一起,

在a標籤最終被拼接時,title的屬性會被封裝到雙引號中,這樣我們就可以構造數據使其閉合,從而執行js

Payload:

WordPress 5.1 CSRF to RCE 漏洞詳解

被雙引號包裹後

WordPress 5.1 CSRF to RCE 漏洞詳解

單鼠標放置時,js執行

但是這個wp_rel_nofollow_callback哪裡來的呢?

看一下wordpress對comment_content都採用了哪些默認的過濾器

\\wp-includes\\default-filters.php

WordPress 5.1 CSRF to RCE 漏洞詳解

WordPress 5.1 CSRF to RCE 漏洞詳解

上圖三個分別是:

wp_rel_nofollow

convert_invalid_entities

balanceTags

看下wp_rel_nofollow

WordPress 5.1 CSRF to RCE 漏洞詳解

wp_rel_nofollow_callback是在這裡被加載並使用的


結語


最後,整理下流程


此次漏洞的流程是:

(超級管理員&不合法nonce) ->(wp_filter_post_kses)->(’rel’屬性在白名單中逃逸)->(wordpress加載默認評論內容過濾器wp_rel_nofollow)->(加載wp_rel_nofollow_callback) ->(未過濾並用雙引號包裹title值)->(js執行)->(RCE


分享到:


相關文章: