RMI源码概述
RMI源码概述
https://docs.oracle.com/javase/tutorial/rmi/overview.html
https://www.javasec.org/javase/RMI/
远程调用方法
RMI 应用程序通常包含两个独立的程序,一个服务器和一个客户端。典型的服务器程序创建一些远程对象,使对这些对象的引用可访问,并等待客户端调用这些对象上的方法。典型的客户端程序获取对服务器上一个或多个远程对象的远程引用,然后调用它们上的方法。RMI 提供了服务器和客户端通信和来回传递信息的机制。这样的应用程序有时被称为分布式对象应用程序。
分布式对象应用需要做到以下几点:
- 定位远程对象。应用程序可以使用各种机制来获取对远程对象的引用。例如,应用程序可以使用 RMI 的简单命名工具 RMI 注册表注册其远程对象。或者,应用程序可以传递和返回远程对象引用作为其他远程调用的一部分。
- 与远程对象通信。远程对象之间的通信细节由 RMI 处理。对于程序员来说,远程通信看起来类似于常规的 Java 方法调用。
- 为传递的对象加载类定义。因为 RMI 允许来回传递对象,所以它提供了加载对象的类定义以及传输对象数据的机制。
相当于封装了一个远程通信
RMI
底层通讯采用了Stub(运行在客户端)
和Skeleton(运行在服务端)
机制,RMI
调用远程方法的大致如下:
RMI客户端
在调用远程方法时会先创建Stub(sun.rmi.registry.RegistryImpl_Stub)
。Stub
会将Remote
对象传递给远程引用层(java.rmi.server.RemoteRef)
并创建java.rmi.server.RemoteCall(远程调用)
对象。RemoteCall
序列化RMI服务名称
、Remote
对象。RMI客户端
的远程引用层
传输RemoteCall
序列化后的请求信息通过Socket
连接的方式传输到RMI服务端
的远程引用层
。RMI服务端
的远程引用层(sun.rmi.server.UnicastServerRef)
收到请求会请求传递给Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)
。Skeleton
调用RemoteCall
反序列化RMI客户端
传过来的序列化。Skeleton
处理客户端请求:bind
、list
、lookup
、rebind
、unbind
,如果是lookup
则查找RMI服务名
绑定的接口对象,序列化该对象并通过RemoteCall
传输到客户端。RMI客户端
反序列化服务端结果,获取远程对象的引用。RMI客户端
调用远程方法,RMI服务端
反射调用RMI服务实现类
的对应方法并序列化执行结果返回给客户端。RMI客户端
反序列化RMI
远程方法调用结果。
demo
1 | import java.rmi.AlreadyBoundException; |
1 | import java.rmi.AlreadyBoundException; |
定义相同的接口
1 | import java.rmi.Remote; |
1 | import com.sun.javaws.IconUtil; |
可以看到做了一个向远程服务端传输了hello,服务端做了一个大写处理
RMI是由序列化和反序列化实现的。
RMI创建远程服务
https://xz.aliyun.com/t/6660#toc-6
我们分析是如何创建远程对象的
首先到了父类的构造函数
跟进去
看构造函数LiveRef
可以看到TCPEndpoint
是用来处理网络请求
我们再看LiveRef
可以看到给赋了值
理清楚了LiveRef
,我们再返回
可以看到创建的值
继续返回到exportObject
一直在赋值
壳痛看到创建了stub
代理
看创建流程
可以看到核心还是LiveRef
可以看到创建了一个动态代理
创建动态代理,还是封装的LiveRef
最后可以看总封装Target
我们继续往下跟
可以看到listen
跟进去,可以看到直接起连接了
开启线程,等待连接
再一路跟下去
我们分析objTable
和implTable
可以看到是一个用来存储的表
可以看到ip和端口已经赋好值了
端口由于未指定,会随机一个端口
整体流程就是如上,最后到走完
创建注册中心+绑定
创建注册中心
直接分析代码原理
创建了注册中心
继续跟进去
和创建远程服务步骤基本一样
区别就是stub那里会不一样
我们按上面的思路跟一下
这里会直接进入判断,和前面不一样的地方,判断了这个
可以看到基本上和前面都是一样的
我们继续往下跟
Util.createSkeleton
继续理解这幅图
给了一个skel
对象
和前面差不多创建了一个Target
然后再一路跟下去
我们直接看objTable
默认创建一个分布式垃圾回收
远程服务的对象
创建注册中心的对象
绑定
然后就是一个绑定过程
可以看到put的值
客户端请求注册中心
我这边两边改了下端口
liveRef
这边就是直接本地封装了一下
这边又调了一个createProxy
,和前面流程是一样的
可以看又注册了一个
下一步是获取远程对象
创建一个远程连接
调了call.executeCall()
然后再看上层的逻辑,用了一个反序列化读取,var22是动态代理,这是一个反序列化的点
还要一个点就是,如果是一个异常的类,然后就会客户端进行一个反序列化,所以说只要调用了invoke
就可能被攻击
最后的结果就是
客户端请求服务端
我们继续调试,一路上到了invoke
和前面差不多,再跟下去
然后看marshalValue
基本上就是一个序列化的逻辑
最后看到还是调用了call.executeCall()
,也就是我们前面看到的反序列化的点还是存在
看unmarshalValue
可以看到这里也可能有一个反序列化的点
可以看到值被反序列化了
客户端请求注册中心
这边直接走到listen
我们分析的是客户端请求服务端来调试
可以看到已经开始解析字段了
可以看到会获取ObjectTable
的target
直接下个断点,然后客户端请求
可以看到直接断到了断点
我们可以看到target
的值
获取分发器
一路跟下去
最后到了
可以分析dispatch
传的是2
所以是调用case2
可以看到有一个反序列化点,反序列化读出了,所以这是一个可以攻击的点
客户端处理stub,服务端处理skel
客户端请求服务端-服务端
这边会继续调传target
再一路跟下去,不同的就是skel为null
继续可以看到获取了我们定义的远程方法
可以看到和前面一样,会把传参反序列化出来
可以看到做了一个反序列化
可以看到传入的参数和进行了远程调用
最后服务端把返回值序列化传输
客户端请求服务端-DGC(RMI分布式垃圾回收)
下个断点
可以看到和前面创建代理的时候差不多
还是创建了一个类stub
然后再put到了target
我们看DGCImpl_Stub
可以看到都调用了invoke,前面我们知道invoke里面是有反序列化的点的
我们再看DGC的服务端,也有反序列化点,也就是说都是可以被攻击的