作者:Lucifaer
原文鏈接:https://lucifaer.com/2020/02/20/Java%20CORBA%E7%A0%94%E7%A9%B6/

在說到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

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

其關系如下圖所示:

其中:

  1. HelloOperations接口中定義了Hello.idl文件中所聲明的sayHello() 方法。

  2. Hello繼承于HelloOperations

  3. _HelloStub實現了Hello接口,client side將使用該類以調用servant sideHello接口的具體實現。

  4. 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調用。

通過NameServer生成方式:

    java
    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));

通過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);
或

```java
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"));

```

通過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請求。而惡意攻擊者在嘗試進行攻擊時可能產生如下的反常動作:

  • 非白名單ip向weblogic發送GIOP請求
  • 非白名單ip向weblogic發送COSNAMING請求
  • 白名單ip但是非開發機ip向weblogic發送COSNAMING請求

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

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

Reference

  1. http://weinan.io/2017/05/03/corba-iiop.html
  2. https://docs.oracle.com/javase/7/docs/technotes/guides/rmi-iiop/tutorial.html#7738
  3. https://docs.oracle.com/javase/8/docs/technotes/guides/idl/GShome.html
  4. https://www.omg.org/corba/faq.htm
  5. https://en.wikipedia.org/wiki/Common_Object_Request_Broker_Architecture

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