作者:天融信阿爾法實驗室
公眾號:https://mp.weixin.qq.com/s/jqdXGIG9SRKniI_2ZhKNJQ

一、背景介紹

social-warfare是一款 WordPress社交分享按鈕插件。 不同于大多數WordPress社交分享插件,social-warfar最大的優勢在于其輕便性與高效性。它不會像其他共享插件一樣減慢網站速度,這也是很多用戶使用其作為自己網站社交分享插件的原因。

該插件被wordpress用戶廣泛的應用: 從官網看,該插件官方的統計是超過90萬的下載量

1.1漏洞描述

social-warfare <= 3.5.2版本中,程序沒有對傳入參數進行嚴格控制以及過濾,導致攻擊者可構造惡意payload,無需后臺權限,直接造成遠程命令執行漏洞。

攻擊成功的條件只需要如下兩條:
1. 目標wordpress站點上安裝有social-warfare
2. social-warfare插件的版本小于或等于3.5.2

只要符合以上兩個條件,無需復雜的payload構造,即可通過簡簡單單的一個get請求,遠程執行任意代碼。
與wordpress自身漏洞修補不同,對于插件的漏洞,wordpress并不會在后臺對該插件進行自動升級,僅僅是提示有新版本可用。 簡言之,由于該機制的存在,目前還有大部分使用該插件的站長,所使用著仍存在漏洞版本的social-warfare插件,面臨著被攻擊的風險。
于此同時,這個漏洞,還是一個洞中洞,開發者的一連串失誤,將該漏洞威脅等級逐步增高。

1.2受影響的系統版本

social-warfare<= 3.5.2

1.3.漏洞編號

CVE-2019-9978

二、漏洞細節

social-warfare安裝后如下圖 如圖中紅框所見,該插件提供了一個簡潔易用的分享功能欄。

首先,通過github的commit記錄,找到漏洞觸發點

漏洞觸發點位于/wp-content/plugins/social-warfare-3.5.2/lib/utilities/SWP_Database_Migration.php中的debug_parameters方法中

首先分析下debug_parameters方法 該方法提供了一種允許更容易調試數據庫遷移功能的方法。

先來看下get_user_options功能的代碼塊 此處功能模塊加載 wp-content/plugins/social-warfare-3.5.2/lib/utilities/SWP_Database_Migration.phpinitialize_database方法中的$defaults數組中的配置信息,如下圖

在訪問與執行該功能模塊后,返回相應的配置信息

接下來分析漏洞觸發點 位于如下圖中的if分支中 也就是在’load_options’這個功能模塊中。該功能模塊,是開發者用來調試數據庫遷移功能的,在對用戶實現實際的業務功能中,該模塊并沒有被使用過。

逐行分析下此功能模塊 首先,可以看到如下圖代碼塊: 如紅框中所見,這里的代碼看起來,需要通過is_admin()方法的校驗。看起來,這里需要有admin權限才可以執行后續代碼觸發漏洞。按照以往經驗,這是一個需要后臺權限才可以代碼執行的漏洞(但這里的推測并不正確,具體的見下文分析)

緊接著,通過file_get_contents方法,發送請求 其中的$_GET[‘swp_url’]我們可控,例如:

http://127.0.0.1/1.php

這樣file_get_contents會訪問

http://127.0.0.1/1.php?swp_debug=get_user_options

并將我們構造好的payload傳遞給$options變量 到此為止,我們通過構造鏈接傳入file_get_contents,達到完全可控$options變量中的內容的目的

接下來,會從$options變量中提取出內容,并進行解析,如下圖 隨后,將解析出的$options值拼接后賦予$array

如使用我們案例中的1.php,那么$array的值為

return phpinfo()

接下來,$array中的值會傳遞入eval中,造成代碼執行

實際效果如下圖

漏洞分析到此結束,本次漏洞影響很大,但漏洞自身沒有什么亮點

接下來,看一下官方是如何修補的
通過github的commit記錄,獲取此次的修補方案。 此次修補,將lib/utilities/SWP_Database_Migration.php中的221-284行,將debug_parameters方法中存在問題的load_options模塊代碼全部刪除 所以不存在繞過補丁的可能性。

在分析此漏洞時,有幾處有意思的地方,和大家分享一下:
思考一:
先來看下如下操作:
首先,我們退出wordpress登陸 可見,此時我們并沒有登陸,也沒有admin權限
接著,我們訪問poc

http://127.0.0.1/wordpress/wp-admin/admin-post.php?swp_debug=load_options&swp_url=http://127.0.0.1/1.php

payload仍然可以觸發
還記得上文此處 在漏洞分析環節,我們的猜測是,由于is_admin方法的校驗,此處應該是后臺漏洞,但是在沒有登陸的情況下,仍然觸發了。
這是為什么呢?
原因如下: 先來看看is_admin方法是如何實現的
位于/wp-includes/load.php 可以看到,有一個if-elseif判斷
elseif中判斷defined (‘WP_ADMIN’)的值
由于我們構造的payload,入口是admin-post.php

看一下admin-post.php 第3行將WP_ADMIN定義為true

也就是說,is_admin方法,檢查的是:此時運行時常量WP_ADMIN的值是否為true。
在wordpress中,WP_ADMIN只是用來標識該文件是否為后臺文件。大多數后臺文件,都會在腳本中定義WP_ADMIN為true(例如wp-admin目錄下的admin-post.php等), 因此is_admin方法檢測通過時,只能說明此是通過后臺文件作為入口,調用debug_parameters方法,并不能有效的驗證此時訪問者的身份是否是admin

前臺index.php無法觸發

wp-admin目錄下的about.php可以觸發

可見,wp-admin下任意文件為入口,都可以觸發該漏洞,也就是說,在構造payload以及進行防護時,需要注意

http://127.0.0.1/wordpress/wp-admin/[xxx].php?swp_debug=load_options&swp_url=http://127.0.0.1/1.php

這里xxx可以是絕大多數后臺php文件

思考二: 訪問http://127.0.0.1/wordpress/index.php?swp_debug=get_user_options 時,是如何將get請求中的swp_debug=get_user_optionsget_user_options功能模塊關聯起來,調用此功能模塊執行相應的功能呢?

同理,當訪問http://127.0.0.1/wordpress/index.php?swp_debug=load_options 時,后臺是如何解析get請求,并找到load_options模塊的?

開始的時候,筆者以為是有相關的路由配置(類似于django中的url解析),或者說是類似MVC結構中的控制器(類似thinkphp中的url普通模式http://localhost/?m=home&c=user&a=login&var=value)這樣的結構,但實際真相很簡單:

見下圖,SWP_Utility::debug方法

debug_parameters方法中的所有if分支中逐個執行debug方法,逐個將debug方法內注冊的值(’load_options’’get_user_options’等)和get請求中swp_debug的值進行比較,如果一樣,則執行該功能模塊的代碼,如果不一樣,則進入下個if中。道理同等于switch

回顧: 此次漏洞,粗看很無趣,細看很有意思
首先,傳入file_get_contents中內容沒有被限制,導致可以訪問任意網址
其次,file_get_contents返回結果,沒有經過任何過濾,直接被eval執行
最終,is_admin方法,本來應該將此漏洞限制在后臺,但錯誤的權限控制,導致無需后臺權限的代碼執行
而且,開發者根本不改,直接刪除功能模塊了事

三、修復建議

目前官方已修復該漏洞,可從官網下載最新版本。由于官方已經將存在漏洞的代碼段徹底刪除,不存在后續補丁繞過等問題。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/919/