Java RMI 笔记

- 4 mins

0x01 简介

RMI (Java Remote Method Invocation) Java 远程方法调用,是一种允许一个 JVM 上的 object 调用另一个 JVM 上 object 方法的机制

RMI 可以使用以下协议实现:

RMI 程序通常包括

官方文档中的图例

the RMI system, using an existing web server, communicates from serve to client and from client to server

通常 RMI server 和 registry 运行在同一个 host 的不同端口上

RMI Registry 默认运行在 1099 端口上

RMI URL rmi://hostname:port/remoteObjectName

具体参考 RMI Overview

0x02 示例

参考 Getting Started Using Java RMI

定义 remote 接口和方法

package com.b1ngz.sec.rmi;

import java.rmi.Remote;
import java.rmi.RemoteException;

public interface Hello extends Remote {
    String sayHello() throws RemoteException;
}

需要实现 Remote 接口,接口方法需要抛出 RemoteException 或其父类的异常

实现 server

package com.b1ngz.sec.rmi;

import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class Server implements Hello {
    public static void main(String[] args) {
        try {
            Server obj = new Server();
            Hello stub = (Hello) UnicastRemoteObject.exportObject(obj, 56666);
            Registry registry = LocateRegistry.createRegistry(11099);
            registry.bind("Hello", stub);

        } catch (Exception e) {
            System.out.println("Server Exception: " + e.toString());
            e.printStackTrace();
        }
    }

    public String sayHello() throws RemoteException {
        return "Hello, World";
    }
}

Server 类实现了 Hello 接口,在 main 函数中创建并导出 remote object,接着将 remote object 注册到 RMI registry 中

UnicastRemoteObject.exportObject(obj, 56666) 方法执行完后,会运行 rmi server,监听在本地 56666 端口,等待 client 的请求。exportObject() 方法返回结果为 remote object stub (代理对象,实现了与 Hello 接口同样的方法,包含 rmi server 的 host、port 信息)

LocateRegistry.createRegistry(11099); 执行完后,会创建并启动 RMI registry,监听在本地 11099 端口

registry.bind("Hello", stub);stub 注册到 registry,并与 name Hello 绑定

实现 client

package com.b1ngz.sec.rmi;

import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class Client {

    public static void main(String[] args) {

        String host = "localhost";
        int port = 11099;
        try {
            Registry registry = LocateRegistry.getRegistry(host, port);
            Hello stub = (Hello) registry.lookup("Hello");
            String response = stub.sayHello();
            System.out.println("response: " + response);
        } catch (Exception e) {
            System.err.println("Client exception: " + e.toString());
            e.printStackTrace();
        }
    }
}

LocateRegistry.getRegistry(host, port); 获取 rmi registry

registry.lookup("Hello") 获取 remote object stub

调用 stub 的 sayHello() 方法背后的流程:

0x03 安全

在远程方法调用过程中,参数需要先序列化,从 local JVM 发送到 remote JVM,然后在 remote JVM 上反序列化,执行完后,将结果序列化,发送回 local JVM,因此可能会存在反序列化漏洞

此外,RMI 有一个特性,即当 class 在 receiver 的 JVM 中没有定义时,可以动态从本地 / 远程加载 object class ,在默认情况下 ( JDK 7u21 起),只允许从本地加载,即 java.rmi.server.useCodebaseOnlytrue,并且有 Security Manager 的存在,因此利用比较困难

0x04 Q&A

0x05 参考

b1ngz
rss facebook twitter github weibo youtube mail spotify instagram linkedin google pinterest medium vimeo