【下篇】CVE-2022-26134 Confluence 多種通用型繞過沙箱姿勢可實現命令回顯
CVE-2022-26134 Confluence OGNL RCE 漏洞深入分析和高版本繞過沙箱實現命令回顯
QCyber,公眾號:且聽安全CVE-2022-26134 Confluence OGNL RCE 漏洞深入分析和高版本繞過沙箱實現命令回顯
本文作為下篇,由漏洞空間站入圈小伙伴 ACyber 分享,主要對比不同版本下 Confluence 的 OGNL 解析的不同,并分享沙箱繞過與命令回顯的歷程。
版本差異
熟悉 Confluence 的小伙伴可能在對該漏洞調試時發現一個問題,就是從 7.15 版本開始,無法像低版本一樣直接注入 OGNL 表達式來實現命令執行等效果了,深入代碼分析可以發現,從 7.15 系列開始,Confluence 在解析 OGNL 表達式的位置添加了沙箱,即在 `com.opensymphony.xwork.util.OgnlValueStack#findValue` 存在黑白名單形式的過濾。
7.14 及以下的版本無過濾:

7.15 及以上的版本存在檢查:

所以直接通過調用如 `java.lang.Runtime` 等形式都會被攔截。
沙箱分析
既然知道高版本不適用低版本 RCE 的問題所在,那讓我們來分析以下沙箱原理嘗試繞過。
跟進可以看到安全檢查主要分為以下 4 個部分:

(1)首先檢查緩存的黑白名單是否能匹配,這里默認為空,所以基本可以認為是無感的。
(2)調用 `isUnSafeClass` 方法進一步檢查,跟進如下:


這里采用黑名單方式對調用的類進行檢查。
(3)采用白名單的方式進行檢查,如果屬于白名單的類則直接添加進白名單緩存。
(4)對表達式進行解析,然后遞歸式的分析表達式 `tree` 的每個結點:

跟進 `containsUnsafeExpression` 方法:

首先采用黑名單方式過濾掉靜態變量、創建對象、賦值操作:

然后分別對靜態方法調用、屬性調用、方法調用、變量調用、常量調用等進行檢查,尤其靜態方法調用的位置采用白名單對允許調用靜態方法的類進行限制,所以 OGNL 表達式常規的通過 `@[類名]@[方法名]` 調用如 `java.lang.Runtime` 的形式無法使用,且 `#request` 、 `#context` 等直接調用 OGNL 上下文變量的形式也在變量調用檢查中被禁止。
沙箱繞過
這里得到了 QCyber 師傅的幫助,解決了高版本沙箱繞過問題。其實分析了上述沙箱機制之后,我們還是可以想到通過反射的方式來調用一些被禁止的類或方法。結合 OGNL 調用靜態方法時需要用到 `@` 符號,所以在剛開始調試時,很顯而易見的使用了 `@java.lang.Class@forName` 的形式,但可想而知是無法滿足過沙箱條件的。
但是 QCyber 師傅提供了一種方法,就是 OGNL 表達式里直接使用 `Class.forName` 的形式,經過測試,這種形式不僅可以繞過沙箱檢測(不屬于 OGNL 表達式里的靜態方法調用形式,所以可以過靜態調用的檢查,也沒有出現 `java.lang` 等形式,滿足了黑名單的檢查等),還可以被 OGNL 正常解析。
但是直接用 `Class.forName` 調用 `java.lang.Runtime` 等還是不行,因為在沙箱第 4 步對 OGNL 表達式各個 `node` 檢查時仍然會檢查到黑名單類。經過和各位師傅的探討和交流,總結出以下 4 種方式:
0x01 方式1:使用正常功能類
調用 Confuence 自帶的正常功能類。可以嘗試尋找全局變量或者函數進行修改,可能達到惡意目的。比如 `com.atlassian.confluence.util.GeneralUtil` 類不在黑名單中,里面存在多個靜態方法,比如可以構造 payload 實現添加任意 Cookie 值:

0x02 方式2:字符串拼接
`Class.forName('java.lang.Runtime')` 這種形式之所以不行,是因為在對常量調用檢查時匹配到調用了 `java.lang.Runtime` 黑名單。所以這里可以通過字符串拼接的形式分割 `java.lang.Runtime` ,比如 `Class.forName('jav'+'a.lang.Runtime')` 的形式,直接繞開黑名單匹配。
0x03 方式3:ClassLoader
使用不在黑名單中的 `ClassLoader` 類。分析沙箱黑白名單之后,發現 `com.sun.org.apache.bcel.internal.util.ClassLoader` 類不在黑名單中,既然可以拿到 `ClassLoader` 類那就萬事大吉了,通過 `loadClass` 方法加載字節碼,就可以自由發揮了。但是在高版本 jdk 中不適用,比如Confluence 如果使用自帶的 11 版本的 JDK 就無法使用這個方法。分析發現 Confluence 自帶了一個 `org.apache.bcel.util.ClassLoader` ,位于 `xalan.jar` JAR 包中,與 `com.sun.org.apache.bcel.internal.util.ClassLoader` 一樣也可以加載 Java 字節碼。
0x04 方式4:this
研究沙箱黑白名單發現,雖然過濾了 `#request` 、 `#context` 等變量,但是沒有過濾 `this` 。OGNL 表達式是以 ` .` 進行串聯的一個鏈式字符串表達式。而這個表達式在進行計算的時候,從左到右,表達式每一次計算返回的結果成為一個臨時的“當前對象”,并在此臨時對象之上繼續進行計算,直到得到計算結果。而這個臨時的“當前對象”會被存儲在一個叫做 `this` 的變量中,這個 `this` 變量就稱為 `this` 指針。而在本次漏洞觸發位置,通過 `this` 獲得的是 URL 目標 `Action` 對象,當然具體利用效果取決于獲取到的 `Action` 對象。比如利用這種思路可以添加管理員。
回顯構造方式(一)
到了這一步可以說已經適配出了全版本通殺的命令回顯方案,但是就高版本來說不能像低版本一樣直接調用 OGNL 上下文的 `context` 來構造回顯。
那么思考,能不能還用前文討論的低版本的回顯方法,通過 OGNL 表達式解析取得自身的 `context` 變量,然后用 `context` 中的屬性帶回命令執行結果。當然是可以的。
首先經過研究發現 OGNL 表達式解析的關鍵在 `ognl.Ognl#getValue`的第 119 行, `tree` 變量來自于 `com.opensymphony.xwork.util.OgnlUtil#compile` ,所以如果可以直接通過反射調用 `com.opensymphony.xwork.util.OgnlUtil.compile().getValue` 就可以實現低版本命令回顯效果。

要滿足以下 2 個步驟:
- 實現 OGNL 表達式的套娃解析。就是 payload 中通過 `Class.forName` 反射調用 `OgnlUtil.compile().getValue` ;
- 取得當前 `OgnlValueStack` 的 `context` 和 `root`,保證上下文變量相同。
通過回溯堆棧,可以發現 `OgnlValueStack` 類的實例是在 `com.opensymphony.xwork.ActionChainResult.execute` 方法中通過 `ActionContext.getContext().getValueStack()` 取得,通過靜態方法取得這正符合反射調用的需求,細節不再贅述。

通用回顯效果如下:

回顯構造方式(二)
直接利用 Confluence 自帶的 `org.apache.bcel.util.ClassLoader` 加載
BCEL 字節碼,可以執行任意 Java 代碼,效果如下(借用 QCyber 師傅實現的截圖):
