WordPress是使用PHP語言開發的博客平臺,用戶可以在支持PHP和MySQL數據庫的服務器上架設屬於自己的網站。也可以把 WordPress當作一個內容管理系統(CMS)來使用。前些日子,RIPS放了一個WordPress5.1的CSRF漏洞通過本文將對此次CSRF漏洞進行詳細分析,RCE相關的分析見後續分析文章
預備知識
在wordpress中,超級管理員是可以在評論中寫入任何代碼而不被過濾的
比如,在評論中輸入
直接彈框
但是超級管理員在提交評論表單時,wordpress需要校驗其Nonce值
想理解這個漏洞,首先要了解下wordpress的Nonce ( number used once )防禦機制
Wordpress的Nonce ( number used once ) 機制,是用來防止CSRF而引進的。WordPress會為一些請求提供一個隨機數進行校驗,以防止未授權的請求的發生。
來看下wordpress的Nonce機制是如何使用的:
1、使用wp_create_nonce生成 nonce值:
可見,其實nonce值與$i、$action、$uid、$token有關
這裡的$i 是nonce創建的時間相關變量,由wp_nonce_tick()生成,其餘的$action、$uid、$token很好理解。
由這裡我們可以看出,nonce的生成,與其操作也是有關係的
2、將生成的 nonce傳遞給需要提交時驗證的前端模板
3、需要驗證的表單被提交後,驗證其中nonce,例如下圖中,本次漏洞點
Nonce講解完畢,言歸正傳,分析本次漏洞
漏洞分析
理論上,如果沒法通過Nonce驗證,後續的操作會直接被終止,而且在csrf攻擊中,攻擊者是沒有辦法偽造管理員實時的Nonce值。
但從本次漏洞處來看,如下圖
這裡雖然沒有通過Nonce的驗證(wp_verify_nonce),但是並未終止操作。Wordpress在這裡使用了兩個過濾方法對後續的數據進行過濾。
至於為什麼沒有終止,而採用瞭如下的過濾邏輯,據說是因為WordPress其中有一些特殊的功能例如trackbacks and pingbacks會受到該值的影響,筆者沒有進一步考究,感興趣的同學可以自己分析下。
到目前為止,我們雖然沒有合法的nonce值,但我們的payload仍然倖存,
接下來,看看邏輯裡的 kses_init_filters()這個方法
超級管理員&非法Nonce情況:
我們用超級管理員身份提交一個評論,但是改包,把&_wp_unfiltered_html_comment改為空,使其通過不了Nonce校驗,如下圖
果然進入下圖斷點
緊接著,進入如下斷點
使用wp_filter_post_kses對輸入的數據進行過濾
普通用戶情況:
此時用普通用戶進行評論
直接調用wp_filter_kses進行過濾
以上思路以及明朗了
超級管理員&合法nonce ->不做任何過濾
超級管理員&不合法nonce ->wp_filter_post_kses
普通用戶 –>wp_filter_kses
先來看看普通用戶提交和超級管理員無nonce提交時調用的過濾函數有什麼區別
普通用戶提交過濾函數:
超級管理員無nonce提交過濾函數:
可以看出只是wp_kses中第二個參數不同,一個是current_filter(),一個是’post’
這裡不同的,對應wp_kses中,應該是allowed_html參數值
這裡舉個普通用戶評論的例子,普通用戶提交評論,current_filter()方法返回的值是pre_comment_content,也就是說allowed_html參數值為pre_comment_content。可見下圖動態調試結果
對應的,超級管理員無nonce提交時,這裡的allowed_html參數值為”post”
那麼allowed_html值不同,到底會有什麼區別呢?
$allowed_html被傳入wp_kses_split方法
進一步看wp_kses_split
注意到這裡$pass_allowed_html = $allowed_html;
現在$allowed_html傳給了$pass_allowed_html
我們要看看這兩個不同的$allowed_html最終傳遞到哪裡被用到
跟進_wp_kses_split_callback,$allowed_html傳給了wp_kses_split2
跟進wp_kses_split2,$allowed_html被傳給了wp_kses_attr
跟進wp_kses_attr,$allowed_html被傳給了wp_kses_allowed_html
跟進wp_kses_allowed_html
一路跟蹤,到了這裡,$allowed_html終於有作用了
回顧一下,
超級管理員無nonce提交時,這裡的allowed_html參數值為”post”
普通用戶提交評論時, allowed_html參數值為”pre_comment_content”。
首先看超級管理員無nonce提交嗎,allowed_html參數值為”post”,進入post分支
可以看到這裡有一個wp_kses_allowed_html方法,跟進去看看
相當於一個白名單機制,再看看白名單上都有什麼,看看$allowedposttags
這裡’a’標籤運行’rel’屬性
再看看普通用戶提交評論時, allowed_html參數值為”pre_comment_content”情況。
這裡白名單是$allowedtags
只允許’href’與’title’
看到這裡,明白wp_filter_post_kses 、wp_filter_ kses兩個過濾函數有什麼區別了嗎?
可以用’rel’屬性與不可以用’rel’,有什麼區別呢?如何造成這次的csrf呢?看下圖
wp-includes\\formatting.php
可以看到屬性值在沒有被轉義處理的情況下就再次拼接在一起,
在a標籤最終被拼接時,title的屬性會被封裝到雙引號中,這樣我們就可以構造數據使其閉合,從而執行js
Payload:
被雙引號包裹後
單鼠標放置時,js執行
但是這個wp_rel_nofollow_callback哪裡來的呢?
看一下wordpress對comment_content都採用了哪些默認的過濾器
\\wp-includes\\default-filters.php
上圖三個分別是:
wp_rel_nofollow
convert_invalid_entities
balanceTags
看下wp_rel_nofollow
wp_rel_nofollow_callback是在這裡被加載並使用的
結語
最後,整理下流程
此次漏洞的流程是:
(超級管理員&不合法nonce) ->(wp_filter_post_kses)->(’rel’屬性在白名單中逃逸)->(wordpress加載默認評論內容過濾器wp_rel_nofollow)->(加載wp_rel_nofollow_callback) ->(未過濾並用雙引號包裹title值)->(js執行)->(RCE
閱讀更多 暗影實驗室 的文章