在說到JNDI的時候,我們最常接觸到的都是較爲上層的JNDI SPI(服務端提供的接口),除了常用的RMI、LDAP這些服務,還存在CORBA服務,這篇文章的重點就是來學習一下JNDI如何使用CORBA服務,並以儘量詳盡的用例子來解釋清楚如何使用CORBA的各個流程。

0x01 基礎概念

這部分可能會較爲枯燥,但是對後續理解有很大的幫助,我儘量用簡單的話來描述清楚幾個名詞。

1.1 IDL與Java IDL

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

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

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

1.2 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通信的媒介。

0x02 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 可以理解爲具體的內容,內容需要首先在目錄裏面進行註冊,這樣當用戶想要訪問具體內容時只需要首先在目錄中查找到具體內容所註冊的引用(通常爲頁數),這樣就可以利用這個引用快速的找到章節具體的內容。(相信對RMI有所理解的對這種關係不會陌生)

後面我將用一個具體的CORBA通信的demo來具體描述這這三者在通信間的關係。

2.1 建立一個CORBA Demo

在闡述CORBA通信前,首先先建立一個用於調試的demo,方便更加清楚的理解上面的概念,以及理清相關關係,之後會深入分析各部分的具體實現。

2.1.1 編寫IDL

CORBA使用IDL供用戶描述其應用程序的接口,所以在編寫具體實例前,我們需要使用IDL來描述應用的接口,然後通過Java自身提供的 idlj 編譯器將其編譯爲Java類。

這裏的IDL代碼描述了一個module名爲 HelloApp 中存在一個 Hello 接口,接口中有一個 sayHello() 方法。

2.1.2 生成client side

這裏直接使用 idlj 來生成 client side 的java類:

idlj -fclient Hello.idl

該命令會自動生成如下的文件:

其關係如下圖所示:

其中:

  • HelloOperations 接口中定義了 Hello.idl 文件中所聲明的 sayHello() 方法
  • Hello 繼承於 HelloOperations
  • _HelloStub 實現了 Hello 接口, client side 將使用該類以調用 servant sideHello 接口的具體實現。
  • HelloHelper 包含幫助函數,用於處理通過網絡傳輸的對象,例如數據編組與反編組的工作(或者說是編碼與反編碼的工作)。
  • IDL有三種參數傳遞方式:in、out和inout。in類型的參數以及返回結果與Java的參數傳遞方式與結果返回方式完全相同。而out和inout兩種類型的參數允許參數具有返回結果的能力,無法直接映射到Java語言的參數傳遞機制,所以IDL爲out和inout參數提供了一個holder,也就是具體實例中的 HelloHolder

其中關鍵的兩個類便是 _HelloStubHelloHelper 。這裏簡單的敘述一下,後面會詳細的分析這兩個類中的具體邏輯。

首先看先 _HelloStub 或者直接稱其爲 Stub :

這裏先不看 readObjectwriteObject 的部分,主要看一下其中實現的 sayHello() 方法。可以看到這裏實現了 Hello 接口,而此處的 sayHello() 方法並非其具體的實現,具體的實現是保存在 serant side 處的,這裏的 sayHello() 方法更像一個遠程調用真正 sayHello() 方法的“委託人”或者“代理”。

可以注意到關鍵的兩個點是 _request()_invoke() ,而 _request() 完成的流程就是從 naming service 獲取 servant side 的“引用”(簡單來說就是 servant side 所註冊的信息,便於 client side 訪問 servant side 以獲取具體實現類), _invoke() 完成的就是通過“引用”訪問 servant side 以獲取具體實現類。

之後我們看一下 HelloHelper 。在 HelloHelper 中有一個常用且重要的方法,那就是 narrow

代碼很簡單,其接受一個 org.omg.CORBA.Object 對象,返回其 Stub 這裏可能現在比較難理解,簡單看一下 narrow 的使用場景:

關鍵點時 ncRef.resolve_str() ,這裏的 ncRefORBnaming service )返回的一個命名上下文,主要看 resolve_str() 的實現:

可以說基本上與 _HelloStubsayHello() 方法一模一樣。所以可以說這裏是返回一個 Stub 來獲取遠程的具體實現類。

2.1.3 生成servant side

同樣也直接可以用 idlj 來生成:

idlj -fserver Hello.idl

注意到除了 HelloPOA 外,其餘的兩個接口是和 client side 是相同的。

在這裏又要涉及到一個新的概念,那就是POA(Portable Object Adapter)便攜式對象適配器(翻譯會有所誤差),它是CORBA規範的一部分。這裏的這個POA虛類是 servant side 的框架類,它提供了方法幫助我們將具體實現對象註冊到 naming service 上。

具體看一下其代碼,截圖中的代碼是其主要的功能:

着重看紅框所標註的代碼,首先 POAOperations 的實現,也是 org.orm.CORBA.portable.InvokeHandler 的實現,同時繼承於 org.omg.PortableServer.Servant ,這保證了 POA 可以攔截 client side 的請求。

POA 首先定義了一個Hashtable用於存放 Operations 的方法名,當攔截到請求後會觸發 _invoke 方法從Hashtable中以方法名作爲索引獲取 Operations 具體實現的相應方法,之後創建返回包,並通過網絡將其寫入 client side

綜上,我們可以總結一下 idlj 幫助我們所生成的所有類之間的關係:

從圖中我們能看到這些類之間的關係,以及看到 client sideservant side 間所共用的類。不過單單只是這些類是無法完成構成完整的通信的,還需要一些方法來實現一些具體的客戶端和服務方法類。

2.1.4 servant side具體實現

根據前面幾個小結的敘述不難知道 servant side 需要有兩個具體的實現類:

  • HelloOperations 的具體實現,需要具體的實現 sayHello() 方法。
  • servant side 的服務端實現,將具體實現的 HelloOperations 註冊到 naming service

先來看第一個需要實現的類,通過上文我們知道我們具體實現 Operations 的類需要被註冊到 naming service 上,而 POA 作爲一個適配器的工作就是幫助我們完成相應的工作以及完成相應請求的響應,所以這裏只需要創建一個具體實現類 HelloImpl 繼承於 POA 即可:

現在 servant side 的服務類關係及變成了:

現在我們實現了 _HelloStub 要獲取的具體實現類 HelloImpl ,同時又有 HelloPOA 來處理網絡請求(實際上是由ORB完成處理的),接下來就只需要實現一個服務來接收 client side 的請求,並將結果返回給 client side

這裏可以將服務端分爲三部分。

第一部分就是激活 POAManager 。CORBA規範定義 POA 對象是需要利用 ORBnaming service 中獲取的,同時其在 naming service 中的命名是 RootPOA 。所以如上圖中第一個紅框所示,就是初始化 ORB ,並利用 ORB 去訪問 naming service 獲取 RootPOA 之後完成激活。

第二部分就是將具體實現類註冊到 naming service 中,具體實現如第二個紅框所示。首先會實例化 HelloImpl ,然後通過 ORB 將其轉換爲 org.omg.CORBA.Object ,最後封裝成一個 Stub 。之後從 naming service 獲取 NameService 並將其轉換爲命名上下文,將 HelloImpl 的別名Hello及其 Stub 綁定到命名上下文中,至此完成了具體註冊流程。

第三部分就是將server設置爲監聽狀態持續運行,用於攔截並處理 client side 的請求,返回相應的具體實現類。

2.1.5 client side具體實現

通過 servant side 的實現應該可以看出 naming service 只是負責保存具體實例的一個“引用”,如果 client side 想要真正的獲取到具體實現類,就需要首先訪問 naming service 獲取這個“引用”,然後訪問服務端,之後通過POA的交互返回具體的實例。梳理清楚這一部分後 client side 的實現就呼之而出了:

首先和服務端一樣,需要初始化 ORB ,通過 ORB 來獲取 NameService 並將其轉換成命名上下文。之後通過別名在命名上下文中獲取其對應的 Stub ,調用 Stub 中的 sayhello() 方法,這個時候纔會完成 client sideservant side 發送請求, POA 處理請求,並將具體實現的 HelloImpl 包裝返回給 client side

這裏有一個需要注意的, helloImpl = HelloHelper.narrow(ncRef.resolve_str(name)) 返回的是一個 _HelloStub 而非真正的 HelloImpl 。只要理解清楚這一點,會避免很多誤解。

2.1.6 naming service的具體實現

ORBD可以理解爲ORB的守護進程,其主要負責建立客戶端( client side )與服務端( servant side )的關係,同時負責查找指定的IOR(可互操作對象引用,是一種數據結構,是CORBA標準的一部分)。ORBD是由Java原生支持的一個服務,其在整個CORBA通信中充當着 naming service 的作用,可以通過一行命令進行啓動:

$ orbd -ORBInitialPort 端口號 -ORBInitialHost url &(表示是否後臺執行)

2.1.7 執行

當設置並啓動 naming service 後,還需要在 serverclient 中增添一些代碼用來指定ORB在初始化的時候所訪問的ORBD的地址,如:

之後完成編譯並首先運行 server 保證將具體實現類綁定到 orbd 上,然後再運行 client 完成遠程類加載:

至此就完成了CORBA demo的編寫。

2.2 CORBA的通信過程及各部件之間的關係

根據2.1的敘述,我們大致知道了CORBA編寫的流程,同時粗略的瞭解了CORBA的執行流,這一小節就來梳理一下其中的幾種模型以及關係。

2.2.1 CORBA通信過程

首先來看一下CORBA的整體通信過程:

  1. 啓動orbd作爲 naming service ,會創建 name service 服務。
  2. corba serverorbd 發送請求獲取 name service ,協商好通信格式。
  3. orbd 返回保存的 name service
  4. corba server 拿到 name service 後將具體的實現類綁定到 name service 上,這個時候 orbd 會拿到註冊後的信息,這個信息就是IOR。
  5. corba clientorbd 發起請求獲取 name service
  6. orbd 返回保存的 name service
  7. corba clientname service 中查找已經註冊的信息獲取到“引用”的信息( corba server 的地址等),通過 orb 的連接功能將遠程方法調用的請求轉發到 corba server
  8. corba server 通過 orb 接收請求,並利用 POA 攔截請求,將請求中所指定的類封裝好,同樣通過 orb 的連接功能返回給 corba client

2.2.2 orb在通信中的作用

orb 在通信中充當的角色可以用一張圖來表明:

可以看到 orb 就是充當客戶端與服務端通信的一個媒介,而因爲處於不同端的 orb 在不同的階段充當不同的角色,有的時候充當接收請求的服務端,有的時候充當發送請求的客戶端,但是其本質一直都是同一個對象(相對於一端來說)。舉個例子對於 corba client 來說在與 corba server 進行通信的過程中, corba clintorb 在發送請求的時候充當客戶端,在接收返回的時候充當服務端,而 orb 從始至終都是其第一次從 orbd 獲取的一個 orb 。對於這樣具有通用性質的 orb ,稱之爲 common ORB Architecture 也就是通用ORB體系。所以 CORBA 最簡單的解釋就是通用 orb 體系。

2.2.3 Stub及POA的作用

Stubclient side 調用 orb 的媒介, POAservant side 用於攔截 client 請求的媒介,而兩者在結構上其實都是客戶端/服務端調用 orb 的媒介,可以用下面這個圖來說明:

orb 充當客戶端與服務端通信的媒介,而客戶端或服務端想要調用 orb 來發送/處理請求就需要 Stubskeleton ,這兩部分的具體實現就是 StubPOA

StubPOA 分別充當客戶端和服務器的代理,具體的流程如下(以2.1的demo爲例):

  1. client 發起調用: sayHello()
  2. Stub 封裝 client 的調用請求併發送給 orbd
  3. orbd 接受請求,根據 server 端的註冊信息,分派給 server 端處理調用請求
  4. server 端的 orb 接收到請求調用 POA 完成對請求的處理,執行 sayHello() ,並將執行結果進行封裝,傳遞給 orbd
  5. orbd 接收到 server 端的返回後將其傳遞給 Stub
  6. Stub 收到請求後,解析二進制流,提取 server 端的處理結果
  7. Stub 將經過處理後的最終結果返回給 client 調用者

0x03 CORBA流程具體分析

接下來將深入代碼實現層對CORBA流程進行具體的分析,主要是從 client 端進行分析。

如2.1.5中所提及的,client端的實現大致分爲兩部分:

  • 初始化 ORB ,通過 ORB 來獲取 NameService 並將其轉換成命名上下文。
  • 獲取並調用 Stub 中相應的方法,完成rpc流程。

可以發現client的大部分操作都是與 Stub 所關聯的,所以我們需要首先深入的分析 Stub 的相關生成過程,才能理解後面的rpc流程。

3.1 Stub的生成

Stub 有很多種生成方式,這裏列舉三種具有代表性的生成方式:

  • 首先獲取 NameServer ,後通過 resolve_str() 方法生成( NameServer 生成方式)
  • 使用 ORB.string_to_object 生成( ORB 生成方式)
  • 使用 javax.naming.InitialContext.lookup() 生成(JNDI生成方式)

而以上三種方法都可以總結成兩步:

  • orbd 獲取 NameServiceNameService 中包含 IOR
  • 根據 IOR 的信息完成rpc調用。
  1. 通過 NameServer 生成方式:
    Properties properties = new Properties();
     properties.put("org.omg.CORBA.ORBInitialHost", "127.0.0.1");
     properties.put("org.omg.CORBA.ORBInitialPort", "1050");
    
     ORB orb = ORB.init(args, properties);
    
     org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
     NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
    
     String name = "Hello";
     helloImpl = HelloHelper.narrow(ncRef.resolve_str(name));
  2. 通過 ORB

    生成方式:

    ORB orb = ORB.init(args, null);
     org.omg.CORBA.Object obj = orb.string_to_object("corbaname::127.0.0.1:1050#Hello");
     Hello hello = HelloHelper.narrow(obj);

    ORB orb = ORB.init(args, null);
     org.omg.CORBA.Object obj = orb.string_to_object("corbaloc::127.0.0.1:1050");
     NamingContextExt ncRef = NamingContextExtHelper.narrow(obj);
     Hello hello = HelloHelper.narrow(ncRef.resolve_str("Hello"));
  3. 通過JNDI生成方式:
    ORB orb = ORB.init(args, null);
     Hashtable env = new Hashtable(5, 0.75f);
     env.put("java.naming.corba.orb", orb);
     Context ic = new InitialContext(env);
     Hello helloRef = HelloHelper.narrow((org.omg.CORBA.Object)ic.lookup("corbaname::127.0.0.1:1050#Hello"));

通過 NameServer 生成方式我們已經很熟悉了,接下來我們來着重看一下通過ORB的生成方式,其實和 Stub 反序列化處的處理是一樣的:

關鍵點就是在 string_to_object() 方法上,跟進看一下,具體實現在 com.sun.corba.se.impl.orb.ORBImpl

operate 中會對出入的字符串進行協議匹配,這裏支持三種協議:

  • IOR
  • Corbaname
  • CorbalocIOR 最終都會生成一個 Stub

在這裏 IOR 是在獲取到 IOR 後生成 Stub 完成rpc調用的,而真正無需事先聲明獲取 NameService 過程, 直接可以完成rpc調用的就只有 Corbaname 協議和 Corbaloc 協議了 CorbanameCorbaloc 在實現上有相近點,具體體現在對url_str的解析以及處理流上。這裏我們首先看一下 insURLHandler.parseURL() 對於url_str的解析流程:

可以看到 CorbanameURL 的生成過程就是將 corbaname:# 這段內容提取出來重新填充到 corbaloc: 後,也就是說最終與 orbd 通信所利用的協議仍然是 Corbaloc ,之後將 # 後的內容作爲 rootnaming context 的引用名。

接下里我們看一下處理流當中的相似點:

可以看到都是通過 getIORUsingCorbaloc() 方法來從 orbd 獲取IOR的。而在 resolveCorbaname 中又在後續增加了和 NamingService 相同的操作。所以通過這兩部分能看出具體通信使用的是 Corbaloc

3.2 rpc流程

通過上面的分析,我們大致知道了生成 Stub 的幾種方式,其中有非常重要的一個方法 resolve_str() 完成了具體的rpc流程,接下來將詳細的分析一下流程。

resolve_str() 在客戶端的具體實現邏輯在 org.omg.CosNaming._NamingContextExtStub

在紅框所示的這兩行代碼中完成了rpc調用及反序列化流程,其主要完成了根據IOR完成通信初始化、發送請求、接受請求、反序列化等流程,接下來將一個一個詳細的說明。

3.2.1 通信初始化

這一部分的功能實現在 _request() 方法中體現。通信初始化可以簡單的表現在兩個方面:

CorbaMessageMediator
OutputObject

具體跟進一下代碼 _request() 的具體實現在 com.sun.corba.se.impl.protocol.CorbaDelegateImpl#request

這裏可以看到首先設置了客戶端調用信息,之後獲取到 ClientRequestDispatcher 也就是客戶端請求分派器並調用了 beginRequest() 方法,由於 beginRequest() 方法過於長,我將比較重要的代碼直接截下來分析:

首先初始化攔截器,這裏的攔截器主要負責攔截返回信息。

之後根據連接狀態來確定是否需要新建 CorbaConnection ,由於是第一次進行通信,沒有之前的鏈接緩存,所以需要創建 CorbaConnection 。在創建新鏈接後,就創建了 CorbaMessageMediator ,這是完成後續數據處理過程中重要的一環。

緊接着通過 CorbaMessageMediator 來創建 OutputObject ,這裏其實創建的是一個 CDROutputObject

所以底層的數據是由 CDROutputObjectCDRInputObject 來處理的。這一點會在後面的反序列化中有所提及。

完成上述初始化過程後需要首先開啓攔截器,以防止初始片段在消息初始化期間發送。

最後完成消息的初始化:

將序列化字符寫入請求頭中,完成消息的初始化,這裏所調用的序列化是是 OutputStream 的原生序列化過程。

3.2.2 發送並接收請求

發送並接收請求主要是在 _invoke() 方法中完成的:

首先獲取到客戶端請求分派器,之後調用 marshlingComplete() 方法完成具體的處理流程:

這裏涉及到兩個關鍵的處理流程 marshalingComplete1()processResponse()

marshalingComplete1流程

首先先看一下 marshalingComplete1() 流程:

finishSendingRequest() 中完成了請求的發送:

可以看到獲取了連接信息,將 OutputObject 進行發送。

waitForResponse() 完成了等待返回接收返回的功能:

通過標誌位來判斷是否已經接收到了請求,如果接收到請求則把序列化內容進行返回:

processResponse流程

processResponse 的具體實行流程很長,但是關鍵的運行邏輯只是如下的代碼:

這裏的 handleDIIReply 是需要着重說明一下,其中 DII 的全名是 Dynamic Invocation Interface 也就是動態調用接口,這是CORBA調用的一種方式,既可以用 Stub 方式調用,也可以通過 DII 方式調用。目前我們所需要知道的是 handleDIIReply 就是用於處理CORBA調用返回的方法就好:

這裏會判斷調用的請求是否是 DII 請求,如果是,則會對返回結果及參數進行處理,觸發反序列化流程, 這一點屬於 client 端的反序列化利用手法,後面會有文章進行總結, 目前只是將這一個關鍵單拋出來詳細的說一下流程。

這裏的 switch case 就是判斷我們前面所提過的IDL的三種參數傳遞方式,當參數傳遞方式爲 outinout 時將會調用 Any.read_value 方法:

TCUtility.unmarshalIn() 中有一個很長的 switch case ,會根據類型來將調用分發到不同的處理方法中,其中有兩個鏈路:

read_value() 來舉例:

可以看到 read_value() 在選擇具體實現的時候是有分支選項的,這其實都可以通過構造來進行指定,這裏我們只看 IDLJavaSerializationInputStream

會直接觸發JDK原生反序列化。

也就是隻要在 server 端精心構造打包結果,當 client 端發起 DII 的rpc請求處理請求返回時會觸發JDK原生的反序列化流程。

3.2.3 反序列化流程

反序列化觸發在 org.omg.CORBA.ObjectHelper#read() 方法中,最終是調用 CDRInputStream_1_0#read_Object 來處理,這裏我只截關鍵點:

createStubFactory() 會指定class的加載地址爲提取出來的 codebase

可以看到具體的遠程調用邏輯還是使用的RMI完成的。當完成遠程類加載後便初始化 StubFactoryStaticImpl

這裏會設定 stubClass ,後面會使用使用 makeStub() 方法完成實例化。

在完成了遠程類加載後,就需要將遠程的類變爲常規的本地類,這一部分的工作是由 internalIORToObject() 方法完成的:

紅框所示的兩處最終的邏輯都一樣,都是 stubFactory.makeStub() :

我們在 createStubFactory() 中已經將完成遠程類加載的類置爲 stub ,在 makeStub() 方法中則完成將其進行實例化的操作,至此便完成了全部的rpc流程。

3.3 小結

通過上文對代碼的跟蹤,不難看出三端都是通過序列化數據來進行溝通的,都是 CDROutputObjectCDRInputObject 的具體實現。所以說 CDROutputObjectCDRInputObject 是CORBA數據的底層處理類,當在實際序列化/反序列化數據時,具體的處理流程大致可分爲兩類:

  • CDROutputStream_x_x / CDRInputStream_x_x
  • IDLJavaSerializationOutputStream / IDLJavaSerializationInputStream

這裏可以將這兩類簡述爲:

  • CDR打/解包流程
  • JDK serial 序列化/反序列化流程

可以看到只有在JDK serial流程中,纔會觸發CORBA的反序列化流程。CDR更多是用於完成rpc流程。

無論是在接收或者發送的流程中,我們都可以看到本質上都是底層數據( CDROutputObjectCDRInputObject )-> CorbaMessageMediator 的處理過程,具體的發送與接收請求都是通過 CorbaMessageMediator 來管控與攔截的,所以想要具體分析CORBA通信過程中請求的發送與接收方式,只需要以 CorbaMessageMediator 爲入手點即可。

無論 client side 還是 servant 在接收請求時基本上都是通過 com.sun.corba.se.impl.transport.SocketOrChannelConnectionImpl#readcom.sun.corba.se.impl.transport.SocketOrChannelConnectionImpl#doWork 處理請求到 com.sun.corba.se.impl.transport.SocketOrChannelConnectionImpl#dispatch ,後續會因爲message類型的不同而進入到不同的處理邏輯中。在選取處理邏輯時主要憑藉2點:

  • header信息決定的版本
  • message信息決定的具體類型

0x04 CORBA網絡通信分析

縱觀整個CORBA的通信流程,不難看出大致分爲3個部分:

  • orbd 通信獲取 NamingService
  • servant side 註冊
  • rpc通信

在具體的流量中也可以清楚的看到整個過程。(由於我是在本地做的測試,所以在流量中的源地址和目的地址都是127.0.0.1)

這裏的2條流量展現了與 orbd 通信獲取 NamingService 的流程:

這裏着重看一下返回包:

可以看到返回了 RootPOA ,且將 NameService 指向 orbd 處的NC0文件。

在獲取到 NamingService 後,在 servant side 註冊前,有如下兩端流量:

這段流量對應的代碼是:

主要的作用是用於檢查獲取到的 NamingService 是否是 NamingContextExt 的實現。

實現註冊的流量如下:

op=to_name 對應的代碼是:

可以簡單的理解爲設定引用名。

op=rebind 對應的代碼是:

這一部分就是通過GIOP傳輸的CORBA接口的一部分,Wireshark可以將其解碼,並將其協議類型標註爲 COSNAMING ,具體來看一下請求包:

這裏在IOR中我們注意到指定了:

  • type_id :用於指定本次(資料庫或者說是引用)註冊的id(實際上是接口類型,就是用於表示接口的唯一標識符),用於實現類型安全。
  • Profile_hostProfile_portservant side 地址。
  • Profile ID :指定了 profile_data 中的內容,例如這裏的 TAG_INTERNET_IOP 所指定的就是 IIOP Profile

通過IOR信息表示了 servant side 的相關rpc信息。

在rpc流程中的關鍵流量就是rpc調用,這裏不再贅述獲取 NamingService 的流量,直接看遠程調用流量:

這裏涉及到3.2中所說到的發送和接受請求的流程,想要了解詳情可以回看這一部分的內容。簡單來說可以把這一部分理解成如下流程:

  • 根據引用名獲取 servant side 的接口 Stub
  • 利用 Stub 中的代理方法二次發起請求,通過發送方法名在 servant side 調用具體的方法, servant side 將方法的結果返回給 client side 完成rpc調用。

0x05 檢測方式

由於CORBA的數據傳遞與傳統的序列化傳輸方式不同,即在二進制流中沒有 ac ed 00 05 的標識,所以單純從流量的角度是很難識別的,只能從流量上下文中進行識別。

通常可以從這兩個角度來進行判斷:

  • 請求ip是否爲白名單中的ip
  • 是否存在外部ip向 orbd 發送 COSNAMING 請求

以weblogic爲例,正常的CORBA交互模型應爲白名單(業務)ip向weblogic(codebase或中間件)發送rpc請求,完成遠程類加載,同時白名單ip處應該有緩存機制以防止頻繁向weblogic發送GIOP請求。而惡意攻擊者在嘗試進行攻擊時可能產生如下的反常動作:

COSNAMING
COSNAMING

第一點就不贅述了,第二點和第三點解釋一下。通過0x04中對流量的分析,我們知道當一個 servant side 嘗試向 orbd 註冊新的引用時會產生 COSNAMING 類型的流量,那麼 COSNAMING 類型的流量就可以作爲一個判別註冊的標誌,如果是非權限區域(非開發機或者內部雲平臺)的機器嘗試進行註冊一個新的引用的話,就有可能標明存在攻擊嘗試。

當然這並不是一種非常準確且高效的檢測方式,但是由於CORBA的特殊性,除非上RASP或者在終端agent上加行爲檢測規則,想要單純的通過鏡像流量做到監測,是非常難的。

0x06 Reference

相關文章