Java Corba 研究
2020-02-20 15:53

报告编号:B6-2020-022001

报告来源:360-CERT

报告作者:Lucifaer

更新日期:2020-02-20

在说到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的整体通信过程:

CORBA1

  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的流程:

-w1172

这里着重看一下返回包:

-w750

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

在获取到NamingService后,在servant side注册前,有如下两端流量:

-w1175

这段流量对应的代码是:

主要的作用是用于检查获取到的NamingService是否是NamingContextExt的实现。

实现注册的流量如下:

-w1175

op=to_name对应的代码是:

可以简单的理解为设定引用名。

op=rebind对应的代码是:

这一部分就是通过GIOP传输的CORBA接口的一部分,Wireshark可以将其解码,并将其协议类型标注为COSNAMING,具体来看一下请求包:

-w566

这里在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上加行为检测规则,想要单纯的通过镜像流量做到监测,是非常难的。

0x06 Reference