RMI基础
RMI基础
Remote Method Invocation(远程方法调用)
它是 Java 提供的一种用于实现分布式计算的 API
概述
RMI以分布式系统为背景,所以我们先说一下分布式系统
分布式系统的本质是拆分与连接:
- 拆分:把功能拆成微服务,把数据拆分到不同数据库。
- 连接:通过 RPC(如 RMI) 进行通信,通过序列化传递数据,通过共识协议达成一致。
分布式系统就是一组通过网络连接的、互相协作的计算机,但对用户来说,它们就像是一台计算机在提供服务。
为了在流量大的时候,多台机器可以协作
分布式系统的“三大基石“
- CAP 定理
C (Consistency) 一致性:所有节点在同一时刻看到的数据是一样的。
A (Availability) 可用性:系统必须时刻能响应请求(不能卡死或报错)。
P (Partition Tolerance) 分区容错性:如果网络断了(分区了),系统还能不能跑。
RPC(远程过程调用)
分布式系统需要节点间通信(实现让 A 机器调用 B 机器就像调用本地函数一样简单)
共识算法
多台机器如何达成一致
意见不统一的解决办法

RMI 依赖的通信协议
为 JRMP(Java Remote Message Protocol,Java 远程消息交换协议),该协议为 Java 定制,要求服务端与客户端都为 Java 编写。
Stub和Skeleton:
RMI的客户端和服务器并不直接通信,客户与远程对象之间采⽤的代理⽅式进⾏ Socket 通信。为远程对象分别⽣成了客户端代理和服务端代理,
其中位于客户端的代理类称为Stub即存根(包含服务器 Skeleton 信息),位于服务端的代理类称为 Skeleton即⻣⼲⽹。RMI Registry:
RMI注册表,默认监听在 1099端⼝上, Client 通过 Name向 RMI Registry 查询,得到这个绑定关系和对应的 Stub。
RMI流程图
首先是服务端创建远程对象,并注册到Registry注册表中
然后客户端访问服务端的Registry查找注册的远程对象,Registry返回RMI代理所需要的stub,客户端通过这个stub代理调用远程对象的方法,stub去和服务端的skeleton通信
(注意,这里通信的数据是序列化后的数据,接收方会反序列化这个数据,也就是存在反序列化漏洞。)skeleton收到序列化数据并反序列化之后,调用远程对象的方法,并返回执行结果给stub,
(注意,这个执行结果在发给stub之前,也是序列化后的数据,同上,也存在反序列化漏洞,最后stub把结果返回给Client)

实现
需要两个部分:Server & Client
Server (服务端)
写一个接口类
IRemoteObj1
2
3
4
5
6
7
8
9package org.example; import java.rmi.Remote; import java.rmi.RemoteException; public interface IRemoteObj extends Remote { public String sayHello(String keywords) throws RemoteException; }配置接口的实现
RemoteObjImpl1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package org.example; import java.rmi.RemoteException; import java.rmi.server.UnicastRemoteObject; public class RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj { //UnicastRemoteObject是通用远程对象(绑定到RMI就需要继承这个) protected RemoteObjImpl() throws RemoteException { } @Override public String sayHello(String keywords) { //转大写 String unKeywords = keywords.toUpperCase(); System.out.println(unKeywords); return unKeywords; } }UnicastRemoteObject是通用远程对象(绑定到RMI就需要继承这个)
写
RMIServer启动服务1
2
3
4
5
6
7
8
9
10
11
12
13
14
15package org.example; import java.rmi.AlreadyBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RMIServer { public static void main(String[] args) throws RemoteException , AlreadyBoundException { IRemoteObj remoteObj = new RemoteObjImpl();//在服务端创建远程对象,实现与客户端通讯 Registry r= LocateRegistry.createRegistry(1099);//注册中心(1099为固定端口) r.bind("remoteObj", remoteObj);//绑定服务,命名为“remoteObj“ } }服务端创建远程对象之后,能连接到客户端,但是找不到客户端客户端只需要知道注册中心的 IP 和端口,所以需要创建这个注册中心作为中介,将服务端挂到注册中心上
Client(客户端)
写一个接口类
IRemoteObj1
2
3
4
5
6
7
8
9package org.example; import java.rmi.Remote; import java.rmi.RemoteException; public interface IRemoteObj extends Remote { public String sayHello(String keywords) throws RemoteException; }客户端必须拥有一份和服务端一模一样的接口文件
类型安全:Java 是强类型语言。客户端需要知道远程对象有哪些方法、参数是什么类型、返回值是什么类型。
生成代理(Stub):当客户端从注册表(Registry)查找服务时,RMI 会在本地动态创建一个实现了
IRemoteObj的代理对象(Stub)。你的代码其实是和这个接口在交互。
启动
RMIClient,进行调用1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17package org.example; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RMIClient { public static void main(String[] args) throws RemoteException, NotBoundException { //从端口(1099)获取注册中心 Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099); //查找在服务端写的名字”remoteObj“ IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj"); //查到时候,进行调用 remoteObj.sayHello("hello"); } }
注意:
| 方法 | 谁来执行 | 作用 |
|---|---|---|
createRegistry(1099) |
服务端 (Server) | 创建并启动一个新的注册表服务。就像是在店里拉起一个货架。 |
getRegistry("IP", 1099) |
客户端 (Client) | 连接一个已经存在的注册表。就像是顾客走到货架前。 |
在Server & Client中,对于注册表函数是不一样的
一个是创建,一个是连接
测试
运行之后发现成功转大写

引申的安全问题
- 客户端攻击服务端:反序列化漏洞&&服务端攻击客户端:恶意对象回传
- 注册中心攻击:类加载&& 动态类加载安全
流程原理
服务端

第一层:注册中心,哈希表对应 名字———远程对象
第二层:服务端,发布远程对象
第三层:客户端,连接注册中心,调用远程对象
Stub和Skeleton是两个代理:服务端创建 Stub/Skeleton —-Stub 进注册中心—- 客户端领走 Stub —-Stub 连 Skeleton实现访问
创建远程服务
调试
加载(初始化阶段:静态代码块 / 静态变量)→ 每次实例化时先执行构造代码块 → 再执行构造函数
实例化对象时会首先触发类加载,在类加载的初始化阶段进行合并静态代码块和静态变量进行赋值操作,接着才会执行构造代码块,构造函数。
(注意静态变量和静态代码块初始化只执行一次)
所以在强制步入的时候,先进的是类加载,类加载结束最终到了构造函数

到类加载的静态


到了远程对象的构造函数

先到父类的构造器(UnicastRemoteObject)
在构造函数之前先到父类的构造器
默认存在super,先到他父类的

接下来是父类的参数port赋值

此处的目的是为了将远程服务调用在随机的端口(注意:此处不是1099,1099是注册器的默认端口。而现在是远程服务)
然后到了
exportObject这个静态函数
目的就是为了导出对象
因为我们继承了父类UnicastRemoteObject,这个里面包含了导出项目的这个函数exportObject未继承,就需要我们自己手写调用exportObject来实现导出分析一下exportObject的返回的两个参数吧
obj是这个对象(
RemoteObjImpl),也就是真正实现代码逻辑的UnicastServerRef是用来读取端口port,处理网络请求的,所以我们现在进到这个类看看进到
UnicastServerRef
创建了一个
LiveRef的对象进到这个里面看看

new 的ObjID是个id,没啥看的,那就直接通过this看构造函数
LiveRef构造函数
分析参数:
objID:就是前面那个id
TCPEndpoint.getLocalEndpoint(port):tcp就是给网络协议
所以可以进到TCPEndpoint看看
TCPEndpoint构造函数
得到一个ip和端口就能处理请求
查看同类(LiveRef)的其他构造函数,进到了最终的构造函数,到这里远程对象就创建好了

可以看一下参数目前的赋值

注意:最重要的函数是LiveRef。 最终的请求处理是在TCPTTransport中
调用
UnicastServerRef的父类
是
UnicastRef
在父类 UnicastRef 中赋值,即刚刚创建的 liveRef
从
UnicastServerRef出来
exportObject的构造函数
接下来看exportObject的构造函数


UnicastServerRefsref是我们刚刚看过的sref里面有
LiveRef过完判断之后,走到了另一个类下的
exportObject1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23public Remote exportObject(Remote impl, Object data, boolean permanent) throws RemoteException { Class<?> implClass = impl.getClass(); Remote stub;//创建stub try { stub = Util.createProxy(implClass, getClientRef(), forceStubUse); } catch (IllegalArgumentException e) { throw new ExportException( "remote object implements illegal remote interface", e); } if (stub instanceof RemoteStub) { setSkeleton(impl); } Target target = new Target(impl, this, stub, ref.getObjID(), permanent); ref.exportObject(target); hashToMethod_Map = hashToMethod_Maps.get(implClass); return stub; }这个地方创建了
stub,后面走到了createProxy,是个代理进到
createProxy
implClass就是远程调用的类
clientRef分装了ref,也就是LiveRef@786
接下来走到判断

stubClassExists(remoteClass)为真,则就往下走到return到这个函数
stubClassExists里面看看
后面的
get.name+"_Stub"也就是说,存在则为真,还得有这个类
_Stub_Stub是java自带的

接下来就是创建动态代理
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18final ClassLoader loader = implClass.getClassLoader(); final Class<?>[] interfaces = getRemoteInterfaces(implClass); final InvocationHandler handler = new RemoteObjectInvocationHandler(clientRef); /* REMIND: private remote interfaces? */ try { return AccessController.doPrivileged(new PrivilegedAction<Remote>() { public Remote run() { return (Remote) Proxy.newProxyInstance(loader, interfaces, handler); }}); } catch (IllegalArgumentException e) { throw new StubNotFoundException("unable to create proxy", e); } }Proxy.newProxyInstance添加拦截器loader类加载器
interfaces接口
handler处理器(此处的处理器就是
ref,也就是LiveRef)下一步,创建
target
可以理解成一个所有东西的封装
可以进去看一下:


TCPTransform处理网络请求
发布
target
先调用
LiveRef这个包下的exportObject
再到
TCPEndpoint这个包下的exportObject
最后到
TCPTransform的包下的exportObject
listen()可以开放端口
先看listen()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36private void listen() throws RemoteException { assert Thread.holdsLock(this); TCPEndpoint ep = getEndpoint(); int port = ep.getPort(); if (server == null) { if (tcpLog.isLoggable(Log.BRIEF)) { tcpLog.log(Log.BRIEF, "(port " + port + ") create server socket"); } try { server = ep.newServerSocket();//创建新的ServerSocket /* * Don't retry ServerSocket if creation fails since * "port in use" will cause export to hang if an * RMIFailureHandler is not installed. */ Thread t = AccessController.doPrivileged( new NewThreadAction(new AcceptLoop(server), "TCP Accept-" + port, true)); t.start(); } catch (java.net.BindException e) { throw new ExportException("Port already in use: " + port, e); } catch (IOException e) { throw new ExportException("Listen failed on port: " + port, e); } } else { // otherwise verify security access to existing server socket SecurityManager sm = System.getSecurityManager(); if (sm != null) { sm.checkListen(port); } } }server = ep.newServerSocket();———–创建新的ServerSocket等待连接
在创建新ServerSocket时候,如果port为0,则随机赋值

- 创建了一个线程
1
2
3
4Thread t = AccessController.doPrivileged( new NewThreadAction(new AcceptLoop(server), "TCP Accept-" + port, true)); t.start();又创建了一个线程,为了连接之后的逻辑
进到
AcceptLoop看看
重写
run跑多线程新开的这个线程是网络请求的线程,上面那个线程是代码的线程
接下来服务端要记录一下

将 target(封装的全部远程对象的信息)put 进两个服务端这边静态的表进行存储
是两个静态map
到目前来说,服务端将封装好的所有东西都发布到了一个随机的port上
服务端在执行
exportObject导出远程对象时,会根据远程接口动态生成一个客户端代理 Stub(本质上是一个Proxy对象)。这个 Stub 会通过 Registry(注册中心) 传递给客户端。客户端拿到这个‘代理’后,调用上面的方法,Stub 会通过内部的LiveRef将请求通过 Socket 发送给服务端,从而间接调用真实对象。
创建注册中心(Registry)

stub
静态方法
createRegistry
通过
RegistryImpl创建port端口看
RegistryImpl
走到这一步还是创建了
UnicastServerRef的lref和LiveRef和上面说的创建远程服务类似
进到setup里面

可以看到和创建远程服务一样,都是调用了
UnicastServerRef的exportObject但是:参数有区别
自定义的创建远程服务(为false)

永久的远程对象(为true)

也就是说创建注册中心中的远程对象是永远存在的
进到setup里面的
exportObject
这里的流程就和上面一样了,创建stub,动态代理
进
createProxy
上面我们直接步过这一步,看的下面的
动态代理以及创建线程这次我们进到
createStub就是直接forname,进行创建Stub,创建的时候放的是
ref
我们注意到两个创建远程服务和创建注册中心中的stub是不同的,但是本质里面存放的都是ref
Registry 一定使用 JDK 自带的
RegistryImpl_Stub;使用静态 stub
普通远程服务因为 JDK 不知道你的实现类,所以默认没有静态 stub,只能运行时动态生成代理 stub。创建注册中心(Registry)是forname创建的stub,创建远程服务是动态代理创建的
Skel
服务端的Skeleton和客户端的stub
接下来走
createSkeleton创建服务端的Skeleton

进去看看


出来之后就到了
target
1
2
3if (stub instanceof RemoteStub) { setSkeleton(impl); }这段代码是为了impl里面的ref放一个skel对象

创建
target1
new Target(impl, this, stub, ref.getObjID(), permanent);impl里面包含
skel对象还放了stub
接下来运行到
putTarget
创建table

看table值

table:是 RMI 的内部登记簿。
所有都在远程调用都在table里面:目前是RMImoteObjImpl RegistryImpl DGCImpl
DGCImpl:分布式垃圾回收(默认创建)
绑定

先检查

再绑定

逻辑是:若有就报异常(已经绑定),没有就接下来
bindings.put(name, obj);(将name put到hashtable里面)此处这个name就是RMIServer命名,我命名为remoteObj
客户端
服务端创建完成之后,客户端调用
- 向注册中心获取远程对象的代理
- 通过获取的代理向服务端做远程调用

客户端请求注册中心-客户端
给ip和端口获取注册中心

传入
ip和端口到liveRef,然后封装一下调用
Util.createProxy和前面说的RegistryImpl_Stub一摸一样我们本来的想法是通过序列化反序列化把注册中心的stub给到客户端
现在是利用再次创建一个一样参数的stub实现
查找获取远程对象(获取服务端绑定到注册中心上的
stub(动态代理))到了
RegistryImpl_Stub.class的lookup
内部逻辑:
传入字符串(我们传入的名称
remoteObj)写到输出流中 进行 序列化
然后进行激活


调用
call.executeCall();这个是真正处理网络请求的

此处

这个地方也有readobject,存在异常从而利用反序列化 获取异常流
接下来注册中心进行
反序列化
也就是说,得到的var20就是远程对象的动态代理
两个反序列化点:
- 客户端通过注册中心获取服务端远程对象的过程 利用的反序列化
(恶意注册中心可以直接利用反序列化攻击客户端)
- call.executeCall()对于 存在的异常 利用反序列化 获取异常流,可以将使用恶意流
(也叫JRMP协议)
根据这两个恶意点可以看出,invoke是每一个都会调用(不只是lookup)所以都会有的call.executeCall()来处理网络请求,存在反序列化的恶意点
最后获取对象
remoteObj显示端口
接下来从客户端连接服务端
客户端请求服务端-客户端
动态代理先到调用处理器里面的invoke方法

到
invokeRemoteMethod
进去之后发现有一个
ref.invoke(
但是这是重载的方法
进
invoke看逻辑有一个marshalValue

他的内部是
writeObject序列化
而序列化的参数就是我们传入的
hello
后面调用
call.executeCall();
也就是说:
1
2
3
4
5stub 方法 InvocationHandler.invoke UnicastRef.invoke call.executeCall() 只要触发远程方法调用,不论 stub 是静态还是动态代理,最终都会进入 UnicastRef.invoke 并执行 call.executeCall,从而把 payload 发往服务端。接下来如果有返回值的话
调用
unmarshalValue
内部:

相当于是把传入的值通过反序列化返回
两个反序列化点:
- JRMP协议(call.executeCall)
- 对应返回值(unmarshalValue)
客户端请求注册中心-注册中心
(注册中心是一个特殊的服务端,看的是skel)
从服务端创建的过程之后,在服务端下断点调试
打断点时考虑 之前客户端代理是 stub,所以这里操作的是注册中心的代理 skel,分析RegistryImpl_Skel 类(注册中心处理客户端交互的逻辑就在这里)
下面看是怎么走到 RegistryImpl_Skel 类:
发起网络请求的时候到了
listen()创建完毕好之后,放到
target,在走listen()开启新线程,里面的run方法
里面有executeAcceptLoop(),到这个里面

又创建了一个线程
进
ConnectionHandler,看这个线程的run方法,里面调用run0()调用
handleMessages
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59void handleMessages(Connection conn, boolean persistent) { int port = getEndpoint().getPort(); try { DataInputStream in = new DataInputStream(conn.getInputStream()); do { int op = in.read(); // transport op if (op == -1) { if (tcpLog.isLoggable(Log.BRIEF)) { tcpLog.log(Log.BRIEF, "(port " + port + ") connection closed"); } break; } if (tcpLog.isLoggable(Log.BRIEF)) { tcpLog.log(Log.BRIEF, "(port " + port + ") op = " + op); } switch (op) { case TransportConstants.Call: // service incoming RMI call RemoteCall call = new StreamRemoteCall(conn); if (serviceCall(call) == false)//默认 return; break; case TransportConstants.Ping: // send ack for ping DataOutputStream out = new DataOutputStream(conn.getOutputStream()); out.writeByte(TransportConstants.PingAck); conn.releaseOutputStream(); break; case TransportConstants.DGCAck: DGCAckHandler.received(UID.read(in)); break; default: throw new IOException("unknown transport op " + op); } } while (persistent); } catch (IOException e) { // exception during processing causes connection to close (below) if (tcpLog.isLoggable(Log.BRIEF)) { tcpLog.log(Log.BRIEF, "(port " + port + ") exception: ", e); } } finally { try { conn.close(); } catch (IOException ex) { // eat exception } } }handleMessages 就是根据字段进入到不同 case,这里面默认进到第一个调用 serviceCall(call)
进
serviceCall获取Target,(serviceCall 里面 getTarget,从静态表中获取记录的 target(存放着所有的信息))

接下来就是获取
disp,从 target 中获取 skel 放到分发器 disp 里面,调用disp.dispatch(impl, call);进dispatch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113public void dispatch(Remote obj, RemoteCall call) throws IOException { // positive operation number in 1.1 stubs; // negative version number in 1.2 stubs and beyond... int num; long op; try { // read remote call header ObjectInput in; try { in = call.getInputStream(); num = in.readInt(); if (num >= 0) { if (skel != null) { oldDispatch(obj, call, num); return; } else { throw new UnmarshalException( "skeleton class not found but required " + "for client version"); } } op = in.readLong(); } catch (Exception readEx) { throw new UnmarshalException("error unmarshalling call header", readEx); } /* * Since only system classes (with null class loaders) will be on * the execution stack during parameter unmarshalling for the 1.2 * stub protocol, tell the MarshalInputStream not to bother trying * to resolve classes using its superclasses's default method of * consulting the first non-null class loader on the stack. */ MarshalInputStream marshalStream = (MarshalInputStream) in; marshalStream.skipDefaultResolveClass(); Method method = hashToMethod_Map.get(op); if (method == null) { throw new UnmarshalException("unrecognized method hash: " + "method not supported by remote object"); } // if calls are being logged, write out object id and operation logCall(obj, method); // unmarshal parameters Class<?>[] types = method.getParameterTypes(); Object[] params = new Object[types.length]; try { unmarshalCustomCallData(in); for (int i = 0; i < types.length; i++) { params[i] = unmarshalValue(types[i], in); } } catch (java.io.IOException e) { throw new UnmarshalException( "error unmarshalling arguments", e); } catch (ClassNotFoundException e) { throw new UnmarshalException( "error unmarshalling arguments", e); } finally { call.releaseInputStream(); } // make upcall on remote object Object result; try { result = method.invoke(obj, params); } catch (InvocationTargetException e) { throw e.getTargetException(); } // marshal return value try { ObjectOutput out = call.getResultStream(true); Class<?> rtype = method.getReturnType(); if (rtype != void.class) { marshalValue(rtype, result, out); } } catch (IOException ex) { throw new MarshalException("error marshalling return", ex); /* * This throw is problematic because when it is caught below, * we attempt to marshal it back to the client, but at this * point, a "normal return" has already been indicated, * so marshalling an exception will corrupt the stream. * This was the case with skeletons as well; there is no * immediately obvious solution without a protocol change. */ } } catch (Throwable e) { logCallException(e); ObjectOutput out = call.getResultStream(false); if (e instanceof Error) { e = new ServerError( "Error occurred in server thread", (Error) e); } else if (e instanceof RemoteException) { e = new ServerException( "RemoteException occurred in server thread", (Exception) e); } if (suppressStackTraces) { clearStackTraces(e); } out.writeObject(e); } finally { call.releaseInputStream(); // in case skeleton doesn't call.releaseOutputStream(); } }然后
serviceCall方法中调用disp.dispatch(impl, call)(在里面 skel 不为 null 进入 if 调
oldDispatch, 传的第三个参数 num 为 2)进
oldDispatch调skel.dispatch
查看函数dispatch的逻辑(在
RegistryImpl_Skel)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception { if (var4 != 4905912898345647071L) { throw new SkeletonMismatchException("interface hash mismatch"); } else { RegistryImpl var6 = (RegistryImpl)var1; switch (var3) { case 0: String var100; Remote var103; try { ObjectInput var105 = var2.getInputStream(); var100 = (String)var105.readObject(); var103 = (Remote)var105.readObject(); } catch (IOException var94) { throw new UnmarshalException("error unmarshalling arguments", var94); } catch (ClassNotFoundException var95) { throw new UnmarshalException("error unmarshalling arguments", var95); } finally { var2.releaseInputStream(); } var6.bind(var100, var103); try { var2.getResultStream(true); break; } catch (IOException var93) { throw new MarshalException("error marshalling return", var93); } case 1: var2.releaseInputStream(); String[] var99 = var6.list(); try { ObjectOutput var102 = var2.getResultStream(true); var102.writeObject(var99); break; } catch (IOException var92) { throw new MarshalException("error marshalling return", var92); } case 2: String var98; try { ObjectInput var104 = var2.getInputStream(); var98 = (String)var104.readObject(); } catch (IOException var89) { throw new UnmarshalException("error unmarshalling arguments", var89); } catch (ClassNotFoundException var90) { throw new UnmarshalException("error unmarshalling arguments", var90); } finally { var2.releaseInputStream(); } Remote var101 = var6.lookup(var98); try { ObjectOutput var9 = var2.getResultStream(true); var9.writeObject(var101); break; } catch (IOException var88) { throw new MarshalException("error marshalling return", var88); } case 3: Remote var8; String var97; try { ObjectInput var11 = var2.getInputStream(); var97 = (String)var11.readObject(); var8 = (Remote)var11.readObject(); } catch (IOException var85) { throw new UnmarshalException("error unmarshalling arguments", var85); } catch (ClassNotFoundException var86) { throw new UnmarshalException("error unmarshalling arguments", var86); } finally { var2.releaseInputStream(); } var6.rebind(var97, var8); try { var2.getResultStream(true); break; } catch (IOException var84) { throw new MarshalException("error marshalling return", var84); } case 4: String var7; try { ObjectInput var10 = var2.getInputStream(); var7 = (String)var10.readObject(); } catch (IOException var81) { throw new UnmarshalException("error unmarshalling arguments", var81); } catch (ClassNotFoundException var82) { throw new UnmarshalException("error unmarshalling arguments", var82); } finally { var2.releaseInputStream(); } var6.unbind(var7); try { var2.getResultStream(true); break; } catch (IOException var80) { throw new MarshalException("error marshalling return", var80); } default: throw new UnmarshalException("invalid method number"); } } }可以看到不同的case对应的不同的方法
目前我们用到的是
case21
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22case 2: String var98; try { ObjectInput var104 = var2.getInputStream(); var98 = (String)var104.readObject(); } catch (IOException var89) { throw new UnmarshalException("error unmarshalling arguments", var89); } catch (ClassNotFoundException var90) { throw new UnmarshalException("error unmarshalling arguments", var90); } finally { var2.releaseInputStream(); } Remote var101 = var6.lookup(var98); try { ObjectOutput var9 = var2.getResultStream(true); var9.writeObject(var101); break; } catch (IOException var88) { throw new MarshalException("error marshalling return", var88); }上面提到服务端的查找的名字
remoteObj传入注册中心利用的序列化此处通过反序列化读取

其他case还有很多
反序列化点:
- 反序列化查找 注册中心中的名字
- 其他case中也存在readobject,对应方法进行触发
客户端请求服务端
客户端在服务端调用远程方法,服务端的处理
跟前面一样都是先 进
oldDispatch调skel.dispatch
看dispatch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public void dispatch(Remote obj, RemoteCall call) throws IOException { // positive operation number in 1.1 stubs; // negative version number in 1.2 stubs and beyond... int num; long op; try { // read remote call header ObjectInput in; try { in = call.getInputStream(); num = in.readInt(); if (num >= 0) { if (skel != null) { oldDispatch(obj, call, num); return; } else { throw new UnmarshalException( "skeleton class not found but required " + "for client version"); } } op = in.readLong(); } catch (Exception readEx) { throw new UnmarshalException("error unmarshalling call header", readEx); }注意,此处
skel是空的 直接就到了op = in.readLong();再后面就是获取远程方法
sayHello
将传入的参数反序列化

unmarshalValue内部:
然后远程调用就结束了
接下来是对于返回值的 序列化传值,反序列化接收
两个反序列化点:
- UnicastServerRef.dispath 中 unmarshalValue 反序列化客户端传的参数造成攻击
- 对于返回值的 序列化传值,反序列化接收也可造成攻击
dgc(分布式垃圾回收)
这个在服务端创建注册中心 提到
客户端请求服务端
接
服务端创建注册中心提到的putTarget
也说到过,我们理论上是有两个
Target,但是又多了一个,他的stub是DGCImpl在创建
RMImoteObjImpl之前,先创建了dgc的代理先创建

由于dgcLog是一个静态元素、

访问类的静态变量时,如果类尚未初始化,JVM 会初始化整个类,执行所有静态代码块和静态变量初始化。
初始化,调用静态代码块

接下来的代码类似
创建注册中心的stub进
createProxy
进
createStub
和
创建注册中心的stub套路就一样了有没有 xxxxx_stub? 有的兄弟有的:

接下来的就和注册中心的一样了,就是端口不一样(注册服务 远程回收服务)
然后就是disp进行调用
从 target 中获取 skel 放到分发器 disp 里面,调用
disp.dispatch(,进oldDispatch调skel.dispatch接下来就是看看
DGCImpl_Stub和DGCImpl_skel的区别DGCImpl_Stub
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package sun.rmi.transport; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.rmi.MarshalException; import java.rmi.RemoteException; import java.rmi.UnexpectedException; import java.rmi.UnmarshalException; import java.rmi.dgc.DGC; import java.rmi.dgc.Lease; import java.rmi.dgc.VMID; import java.rmi.server.ObjID; import java.rmi.server.Operation; import java.rmi.server.RemoteRef; import java.rmi.server.RemoteStub; import java.rmi.server.UID; import java.util.ArrayList; import sun.misc.ObjectInputFilter; import sun.misc.ObjectInputFilter.Status; import sun.rmi.transport.tcp.TCPConnection; public final class DGCImpl_Stub extends RemoteStub implements DGC { private static final Operation[] operations = new Operation[]{new Operation("void clean(java.rmi.server.ObjID[], long, java.rmi.dgc.VMID, boolean)"), new Operation("java.rmi.dgc.Lease dirty(java.rmi.server.ObjID[], long, java.rmi.dgc.Lease)")}; private static final long interfaceHash = -669196253586618813L; private static int DGCCLIENT_MAX_DEPTH = 6; private static int DGCCLIENT_MAX_ARRAY_SIZE = 10000; public DGCImpl_Stub() { } public DGCImpl_Stub(RemoteRef var1) { super(var1); } public void clean(ObjID[] var1, long var2, VMID var4, boolean var5) throws RemoteException { try { StreamRemoteCall var6 = (StreamRemoteCall)this.ref.newCall(this, operations, 0, -669196253586618813L); var6.setObjectInputFilter(DGCImpl_Stub::leaseFilter); try { ObjectOutput var7 = var6.getOutputStream(); var7.writeObject(var1); var7.writeLong(var2); var7.writeObject(var4); var7.writeBoolean(var5); } catch (IOException var8) { throw new MarshalException("error marshalling arguments", var8); } this.ref.invoke(var6); this.ref.done(var6); } catch (RuntimeException var9) { throw var9; } catch (RemoteException var10) { throw var10; } catch (Exception var11) { throw new UnexpectedException("undeclared checked exception", var11); } } public Lease dirty(ObjID[] var1, long var2, Lease var4) throws RemoteException { try { StreamRemoteCall var5 = (StreamRemoteCall)this.ref.newCall(this, operations, 1, -669196253586618813L); var5.setObjectInputFilter(DGCImpl_Stub::leaseFilter); try { ObjectOutput var6 = var5.getOutputStream(); var6.writeObject(var1); var6.writeLong(var2); var6.writeObject(var4); } catch (IOException var16) { throw new MarshalException("error marshalling arguments", var16); } this.ref.invoke(var5); Connection var7 = var5.getConnection(); Lease var22; try { ObjectInput var8 = var5.getInputStream(); var22 = (Lease)var8.readObject(); } catch (IOException | ClassNotFoundException | ClassCastException var17) { if (var7 instanceof TCPConnection) { ((TCPConnection)var7).getChannel().free(var7, false); } var5.discardPendingRefs(); throw new UnmarshalException("error unmarshalling return", var17); } finally { this.ref.done(var5); } return var22; } catch (RuntimeException var19) { throw var19; } catch (RemoteException var20) { throw var20; } catch (Exception var21) { throw new UnexpectedException("undeclared checked exception", var21); } } private static ObjectInputFilter.Status leaseFilter(ObjectInputFilter.FilterInfo var0) { if (var0.depth() > (long)DGCCLIENT_MAX_DEPTH) { return Status.REJECTED; } else { Class var1 = var0.serialClass(); if (var1 == null) { return Status.UNDECIDED; } else { while(var1.isArray()) { if (var0.arrayLength() >= 0L && var0.arrayLength() > (long)DGCCLIENT_MAX_ARRAY_SIZE) { return Status.REJECTED; } var1 = var1.getComponentType(); } if (var1.isPrimitive()) { return Status.ALLOWED; } else { return var1 != UID.class && var1 != VMID.class && var1 != Lease.class && (!Throwable.class.isAssignableFrom(var1) || var1.getClassLoader() != Object.class.getClassLoader()) && var1 != StackTraceElement.class && var1 != ArrayList.class && var1 != Object.class && !var1.getName().equals("java.util.Collections$UnmodifiableList") && !var1.getName().equals("java.util.Collections$UnmodifiableCollection") && !var1.getName().equals("java.util.Collections$UnmodifiableRandomAccessList") ? Status.REJECTED : Status.ALLOWED; } } } } }两个反序列化点:
调用
invoke里面的call.executeCall()(JRMP协议)
所有客户端stub都会被反序列化攻击
反序列化之后, 转化为Lease类型

DGCImpl_skel
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94// // Source code recreated from a .class file by IntelliJ IDEA // (powered by FernFlower decompiler) // package sun.rmi.transport; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.rmi.MarshalException; import java.rmi.Remote; import java.rmi.UnmarshalException; import java.rmi.dgc.Lease; import java.rmi.dgc.VMID; import java.rmi.server.ObjID; import java.rmi.server.Operation; import java.rmi.server.RemoteCall; import java.rmi.server.Skeleton; import java.rmi.server.SkeletonMismatchException; public final class DGCImpl_Skel implements Skeleton { private static final Operation[] operations = new Operation[]{new Operation("void clean(java.rmi.server.ObjID[], long, java.rmi.dgc.VMID, boolean)"), new Operation("java.rmi.dgc.Lease dirty(java.rmi.server.ObjID[], long, java.rmi.dgc.Lease)")}; private static final long interfaceHash = -669196253586618813L; public Operation[] getOperations() { return (Operation[])operations.clone(); } public void dispatch(Remote var1, RemoteCall var2, int var3, long var4) throws Exception { if (var4 != -669196253586618813L) { throw new SkeletonMismatchException("interface hash mismatch"); } else { DGCImpl var6 = (DGCImpl)var1; StreamRemoteCall var7 = (StreamRemoteCall)var2; switch (var3) { case 0: ObjID[] var34; long var35; VMID var36; boolean var38; try { ObjectInput var39 = var7.getInputStream(); var34 = (ObjID[])var39.readObject(); var35 = var39.readLong(); var36 = (VMID)var39.readObject(); var38 = var39.readBoolean(); } catch (IOException | ClassNotFoundException | ClassCastException var32) { var7.discardPendingRefs(); throw new UnmarshalException("error unmarshalling arguments", var32); } finally { var7.releaseInputStream(); } var6.clean(var34, var35, var36, var38); try { var7.getResultStream(true); break; } catch (IOException var31) { throw new MarshalException("error marshalling return", var31); } case 1: ObjID[] var8; long var9; Lease var11; try { ObjectInput var12 = var7.getInputStream(); var8 = (ObjID[])var12.readObject(); var9 = var12.readLong(); var11 = (Lease)var12.readObject(); } catch (IOException | ClassNotFoundException | ClassCastException var29) { var7.discardPendingRefs(); throw new UnmarshalException("error unmarshalling arguments", var29); } finally { var7.releaseInputStream(); } Lease var37 = var6.dirty(var8, var9, var11); try { ObjectOutput var13 = var7.getResultStream(true); var13.writeObject(var37); break; } catch (IOException var28) { throw new MarshalException("error marshalling return", var28); } default: throw new UnmarshalException("invalid method number"); } } } }两个反序列化点:
- case0

- case1

攻击汇总
攻击客户端:
- 客户端查询远程对象时:注册中心攻击客户端
RegistryImpl_Stub.lookup(xxx) - 客户端反序列化服务端返回的方法结果:服务端攻击客户端
UnicastRef.invoke() (JRMP协议) - DGC 服务端攻击客户端
DGCImpl_Stub.dirty() - 客户端发起网络请求后反序列化服务端返回的恶意对象:
call.executeCall() (JRMP协议)
- 客户端查询远程对象时:注册中心攻击客户端
攻击注册中心:
- 客户端获取远程对象时:客户端攻击注册中心
RegistryImpl_Skel.dispatch(xxx)
- 客户端获取远程对象时:客户端攻击注册中心
攻击服务端:
客户端传递方法参数时:客户端攻击服务端
UnicastServerRef.dispatch(xxx)DGC 客户端 攻击服务端
DGCImpl_Skel.dispatch()