针对RMI服务的九重攻击 - 上
字数 2464 2025-08-25 22:59:02

针对RMI服务的九重攻击(上)技术分析文档

前言

本文详细分析针对Java RMI服务的多种攻击方式,重点介绍上篇内容:针对已知RMI接口的三种攻击方式与针对RMI层(RMI注册端、RMI服务端)/DGC层的攻击方法。这些攻击方式主要利用RMI协议的反序列化漏洞,通过不同层面的交互实现远程代码执行。

RMI基础知识回顾

RMI(Remote Method Invocation)是Java的远程方法调用机制,主要由以下组件构成:

  • RMI注册端(Registry):运行在1099端口,负责管理RMI服务
  • RMI服务端(Server):提供远程服务实现
  • RMI客户端(Client):调用远程服务

RMI通信过程中存在多处反序列化点,这些点是攻击的主要入口。

探测利用开放的RMI服务

BaRMIe工具分析

BaRMIe工具提供两种模式:

  1. enum枚举模式:探测RMI服务信息
  2. attack攻击模式:尝试攻击发现的RMI服务

枚举流程

  1. 通过LocateRegistry.getRegistry获取目标RMI注册端
  2. 使用reg.list()获取注册端上所有服务端Endpoint对象
  3. 尝试解绑不存在的服务名,判断是否可操控注册端
  4. 通过代理服务器获取服务端详细信息(类名等)

攻击模块分类

  1. RMI客户端探测利用RMI服务(如Axiom组件的文件操作)
  2. RMI客户端反序列化攻击RMI服务端(利用Object类型参数)
  3. RMI服务端攻击RMI注册端(Bind类攻击)

RMI客户端反序列化攻击RMI服务端

利用Object类型参数

攻击条件

  1. 服务端提供RMI服务,且方法参数为Object类型
  2. 服务端存在可利用的反序列化链

漏洞触发点
服务端在sun.rmi.server.UnicastServerRef#dispatch中处理请求时:

  1. 根据Method hash验证方法是否存在
  2. 遍历入参类型,从输入流反序列化入参
  3. 当参数非基础数据类型时,执行readObject()触发反序列化

绕过Object类型参数限制

研究发现非基础数据类型的参数均可利用,包括:

  • String
  • Integer(注意:Integer不是基础类型,int才是)
  • 数组类型等

实现方式

  1. 修改RMI底层源码
  2. 运行时添加调试器hook客户端
  3. 使用Javassist工具更改字节码
  4. 使用代理替换序列化对象中的参数

RMI服务端反序列化攻击RMI注册端

攻击接口分析

RMI注册端存在4个反序列化触发点:

  1. bind(String, Remote)
  2. lookup(String)
  3. rebind(String, Remote)
  4. unbind(String)

关键发现

  • 不仅Remote类型参数可利用,String参数位置同样可放置payload
  • 攻击不限于bind接口,四个接口均可利用

工具实现分析

BaRMIe - Bind攻击

  1. 建立RMI代理服务器
  2. 重构数据包,将String参数替换为payload
  3. 第二个Remote参数设为null

Ysoserial-RMIRegistryExploit

  1. 使用动态代理将payload封装成Remote接口对象
  2. 通过正常bind接口发送
  3. 动态代理仅用于接口转换,不涉及触发逻辑

RMIattack - 回显实现

  1. 修改CC链底层调用为write()方法
  2. 写入临时class文件
  3. 通过异常机制回显命令执行结果

RMI DGC层反序列化

DGC机制简介

DGC(Distributed Garbage Collection)用于维护远程引用,主要方法:

  • dirty():注册远程引用
  • clean():清除远程引用

攻击原理

漏洞触发点
sun.rmi.transport.DGCImpl_Skel#dispatch中:

  1. dirty和clean方法都会反序列化客户端提供的ObjID[]参数
  2. 通过构造恶意序列化数据触发漏洞

Ysoserial JRMP-Client实现

  1. 建立socket连接
  2. 手动构造DGC协议数据包:
    • 魔术字符:0x4a524d49
    • 协议版本:2
    • 协议类型:0x4c
    • 指令:0x50(RMI call)
  3. 写入DGC固定格式数据
  4. 插入payload对象

JEP290修复与绕过

JEP290限制

在JDK 6u141/7u131/8u121后引入:

  1. RMI注册表过滤器:

    • 深度限制:20层
    • 数组长度限制:10000
    • 白名单:String、Number、Remote、Proxy等
  2. DGC过滤器:

    • 类似注册表过滤器
    • 白名单:ObjID、UID、VMID、Lease

受影响攻击

以下攻击被JEP290阻止:

  1. RMI服务端攻击注册端(bind/rebind/unbind)
  2. DGC层攻击
  3. lookup攻击注册端

例外
RMI客户端利用参数反序列化攻击服务端不受限,因为:

  • 参数反序列化是RMI正常功能
  • 无法使用白名单限制,否则会破坏正常RMI通信

版本差异

  1. 8u141后新增注册端对服务端的地址验证:

    • 先验证再反序列化
    • 使服务端bind攻击失效
    • lookup攻击(客户端发起)不受影响
  2. 但JEP290(8u121)已阻止所有注册端攻击,地址验证改动影响有限

小结(上篇)

  1. 探测利用开放的RMI服务

    • 基于已知组件特征识别
    • 需要预先知道接口定义
  2. RMI客户端反序列化攻击RMI服务端

    • 不限于Object参数,所有非基础类型参数均可利用
    • 通过参数替换技术实现
  3. RMI服务端反序列化攻击RMI注册端

    • 四个接口均可利用
    • String和Remote参数位置均可放置payload
    • 动态代理仅用于接口适配
  4. DGC层攻击

    • 攻击面更广(所有RMI服务都开启DGC)
    • 需要手动构造DGC协议数据包
  5. JEP290影响

    • 阻止了注册端和DGC层攻击
    • 参数反序列化攻击不受限

下篇将重点介绍绕过JEP290的JRMP利用方式及其他高级攻击技术。

针对RMI服务的九重攻击(上)技术分析文档 前言 本文详细分析针对Java RMI服务的多种攻击方式,重点介绍上篇内容:针对已知RMI接口的三种攻击方式与针对RMI层(RMI注册端、RMI服务端)/DGC层的攻击方法。这些攻击方式主要利用RMI协议的反序列化漏洞,通过不同层面的交互实现远程代码执行。 RMI基础知识回顾 RMI(Remote Method Invocation)是Java的远程方法调用机制,主要由以下组件构成: RMI注册端(Registry):运行在1099端口,负责管理RMI服务 RMI服务端(Server):提供远程服务实现 RMI客户端(Client):调用远程服务 RMI通信过程中存在多处反序列化点,这些点是攻击的主要入口。 探测利用开放的RMI服务 BaRMIe工具分析 BaRMIe工具提供两种模式: enum枚举模式:探测RMI服务信息 attack攻击模式:尝试攻击发现的RMI服务 枚举流程 : 通过 LocateRegistry.getRegistry 获取目标RMI注册端 使用 reg.list() 获取注册端上所有服务端Endpoint对象 尝试解绑不存在的服务名,判断是否可操控注册端 通过代理服务器获取服务端详细信息(类名等) 攻击模块分类 : RMI客户端探测利用RMI服务(如Axiom组件的文件操作) RMI客户端反序列化攻击RMI服务端(利用Object类型参数) RMI服务端攻击RMI注册端(Bind类攻击) RMI客户端反序列化攻击RMI服务端 利用Object类型参数 攻击条件 : 服务端提供RMI服务,且方法参数为Object类型 服务端存在可利用的反序列化链 漏洞触发点 : 服务端在 sun.rmi.server.UnicastServerRef#dispatch 中处理请求时: 根据Method hash验证方法是否存在 遍历入参类型,从输入流反序列化入参 当参数非基础数据类型时,执行 readObject() 触发反序列化 绕过Object类型参数限制 研究发现 非基础数据类型 的参数均可利用,包括: String Integer(注意:Integer不是基础类型,int才是) 数组类型等 实现方式 : 修改RMI底层源码 运行时添加调试器hook客户端 使用Javassist工具更改字节码 使用代理替换序列化对象中的参数 RMI服务端反序列化攻击RMI注册端 攻击接口分析 RMI注册端存在4个反序列化触发点: bind(String, Remote) lookup(String) rebind(String, Remote) unbind(String) 关键发现 : 不仅Remote类型参数可利用,String参数位置同样可放置payload 攻击不限于bind接口,四个接口均可利用 工具实现分析 BaRMIe - Bind攻击 : 建立RMI代理服务器 重构数据包,将String参数替换为payload 第二个Remote参数设为null Ysoserial-RMIRegistryExploit : 使用动态代理将payload封装成Remote接口对象 通过正常bind接口发送 动态代理仅用于接口转换,不涉及触发逻辑 RMIattack - 回显实现 : 修改CC链底层调用为write()方法 写入临时class文件 通过异常机制回显命令执行结果 RMI DGC层反序列化 DGC机制简介 DGC(Distributed Garbage Collection)用于维护远程引用,主要方法: dirty() :注册远程引用 clean() :清除远程引用 攻击原理 漏洞触发点 : 在 sun.rmi.transport.DGCImpl_Skel#dispatch 中: dirty和clean方法都会反序列化客户端提供的ObjID[ ]参数 通过构造恶意序列化数据触发漏洞 Ysoserial JRMP-Client实现 : 建立socket连接 手动构造DGC协议数据包: 魔术字符:0x4a524d49 协议版本:2 协议类型:0x4c 指令:0x50(RMI call) 写入DGC固定格式数据 插入payload对象 JEP290修复与绕过 JEP290限制 在JDK 6u141/7u131/8u121后引入: RMI注册表过滤器: 深度限制:20层 数组长度限制:10000 白名单:String、Number、Remote、Proxy等 DGC过滤器: 类似注册表过滤器 白名单:ObjID、UID、VMID、Lease 受影响攻击 以下攻击被JEP290阻止: RMI服务端攻击注册端(bind/rebind/unbind) DGC层攻击 lookup攻击注册端 例外 : RMI客户端利用参数反序列化攻击服务端不受限,因为: 参数反序列化是RMI正常功能 无法使用白名单限制,否则会破坏正常RMI通信 版本差异 8u141后新增注册端对服务端的地址验证: 先验证再反序列化 使服务端bind攻击失效 lookup攻击(客户端发起)不受影响 但JEP290(8u121)已阻止所有注册端攻击,地址验证改动影响有限 小结(上篇) 探测利用开放的RMI服务 : 基于已知组件特征识别 需要预先知道接口定义 RMI客户端反序列化攻击RMI服务端 : 不限于Object参数,所有非基础类型参数均可利用 通过参数替换技术实现 RMI服务端反序列化攻击RMI注册端 : 四个接口均可利用 String和Remote参数位置均可放置payload 动态代理仅用于接口适配 DGC层攻击 : 攻击面更广(所有RMI服务都开启DGC) 需要手动构造DGC协议数据包 JEP290影响 : 阻止了注册端和DGC层攻击 参数反序列化攻击不受限 下篇将重点介绍绕过JEP290的JRMP利用方式及其他高级攻击技术。