wordPress/ target=_blank class=infotextkey>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-includesformatting.php
可以看到屬性值在沒有被轉義處理的情況下就再次拼接在一起,
在a標簽最終被拼接時,title的屬性會被封裝到雙引號中,這樣我們就可以構造數據使其閉合,從而執行js
Payload:
被雙引號包裹后
單鼠標放置時,js執行
但是這個wp_rel_nofollow_callback哪里來的呢?
看一下wordpress對comment_content都采用了哪些默認的過濾器
wp-includesdefault-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