apsry

去留无意,宠辱不惊

0%

RMI源码概述

RMI源码概述

RMI源码概述

https://docs.oracle.com/javase/tutorial/rmi/overview.html

https://www.javasec.org/javase/RMI/

远程调用方法

RMI 应用程序通常包含两个独立的程序,一个服务器和一个客户端。典型的服务器程序创建一些远程对象,使对这些对象的引用可访问,并等待客户端调用这些对象上的方法。典型的客户端程序获取对服务器上一个或多个远程对象的远程引用,然后调用它们上的方法。RMI 提供了服务器和客户端通信和来回传递信息的机制。这样的应用程序有时被称为分布式对象应用程序

分布式对象应用需要做到以下几点:

  • 定位远程对象。应用程序可以使用各种机制来获取对远程对象的引用。例如,应用程序可以使用 RMI 的简单命名工具 RMI 注册表注册其远程对象。或者,应用程序可以传递和返回远程对象引用作为其他远程调用的一部分。
  • 与远程对象通信。远程对象之间的通信细节由 RMI 处理。对于程序员来说,远程通信看起来类似于常规的 Java 方法调用。
  • 为传递的对象加载类定义。因为 RMI 允许来回传递对象,所以它提供了加载对象的类定义以及传输对象数据的机制。

相当于封装了一个远程通信

image-20220405150625155

RMI底层通讯采用了Stub(运行在客户端)Skeleton(运行在服务端)机制,RMI调用远程方法的大致如下:

  1. RMI客户端在调用远程方法时会先创建Stub(sun.rmi.registry.RegistryImpl_Stub)
  2. Stub会将Remote对象传递给远程引用层(java.rmi.server.RemoteRef)并创建java.rmi.server.RemoteCall(远程调用)对象。
  3. RemoteCall序列化RMI服务名称Remote对象。
  4. RMI客户端远程引用层传输RemoteCall序列化后的请求信息通过Socket连接的方式传输到RMI服务端远程引用层
  5. RMI服务端远程引用层(sun.rmi.server.UnicastServerRef)收到请求会请求传递给Skeleton(sun.rmi.registry.RegistryImpl_Skel#dispatch)
  6. Skeleton调用RemoteCall反序列化RMI客户端传过来的序列化。
  7. Skeleton处理客户端请求:bindlistlookuprebindunbind,如果是lookup则查找RMI服务名绑定的接口对象,序列化该对象并通过RemoteCall传输到客户端。
  8. RMI客户端反序列化服务端结果,获取远程对象的引用。
  9. RMI客户端调用远程方法,RMI服务端反射调用RMI服务实现类的对应方法并序列化执行结果返回给客户端。
  10. RMI客户端反序列化RMI远程方法调用结果。

demo

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import java.rmi.AlreadyBoundException;
import java.rmi.NotBoundException;
import java.rmi.Remote;
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 {
//调用远程
Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
IRemoteObj remoteObj = (IRemoteObj) registry.lookup("remoteObj");
remoteObj.sayHello("hello");
}

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import java.rmi.AlreadyBoundException;
import java.rmi.Remote;
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);
r.bind("remoteObj", remoteObj);
}
}

定义相同的接口

1
2
3
4
5
6
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface IRemoteObj extends Remote {
public String sayHello(String keywords) throws RemoteException;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import com.sun.javaws.IconUtil;

import java.rmi.RemoteException;
import java.rmi.server.UnicastRemoteObject;

public class RemoteObjImpl extends UnicastRemoteObject implements IRemoteObj {
public RemoteObjImpl() throws RemoteException{

}
@Override
public String sayHello(String keywords){
String upKeywords = keywords.toUpperCase();
System.out.println(upKeywords);
return upKeywords;
}
}

可以看到做了一个向远程服务端传输了hello,服务端做了一个大写处理

image-20220405154324442

RMI是由序列化和反序列化实现的。

RMI创建远程服务

https://xz.aliyun.com/t/9261

https://xz.aliyun.com/t/6660#toc-6

https://halfblue.github.io/2021/10/26/RMI%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E4%B9%8B%E4%B8%89%E9%A1%BE%E8%8C%85%E5%BA%90-%E6%B5%81%E7%A8%8B%E5%88%86%E6%9E%90/

img

我们分析是如何创建远程对象的

image-20220405173627299

首先到了父类的构造函数

image-20220405173725525

image-20220405173904383

跟进去

image-20220405213423795

image-20220405213557136

看构造函数LiveRef

image-20220405213635967

可以看到TCPEndpoint是用来处理网络请求

image-20220405213718409

我们再看LiveRef

可以看到给赋了值

image-20220405214139681

理清楚了LiveRef,我们再返回

image-20220405214411480

可以看到创建的值

image-20220405214538203

继续返回到exportObject

image-20220405214722378

一直在赋值

image-20220405214823689

壳痛看到创建了stub代理

image-20220405215009087

img

看创建流程

image-20220405215113840

可以看到核心还是LiveRef

image-20220405215203195

可以看到创建了一个动态代理

image-20220405215317612

创建动态代理,还是封装的LiveRef

image-20220405215524996

最后可以看总封装Target

image-20220405215834966

image-20220405215924703

我们继续往下跟

image-20220405220019465

可以看到listen

image-20220405220050077

跟进去,可以看到直接起连接了

image-20220405220153807

开启线程,等待连接

image-20220405220305936

再一路跟下去

image-20220405220806364

我们分析objTableimplTable

可以看到是一个用来存储的表

image-20220405220853355

可以看到ip和端口已经赋好值了

image-20220405221027229

端口由于未指定,会随机一个端口

整体流程就是如上,最后到走完

img

创建注册中心+绑定

创建注册中心

直接分析代码原理

image-20220405222429069

创建了注册中心

image-20220405222502632

继续跟进去

image-20220405223144058

和创建远程服务步骤基本一样

image-20220405223234016

区别就是stub那里会不一样

我们按上面的思路跟一下

image-20220405223924536

这里会直接进入判断,和前面不一样的地方,判断了这个

image-20220405224114704

可以看到基本上和前面都是一样的

image-20220405224309587

我们继续往下跟

image-20220405224401630

Util.createSkeleton

image-20220405224429732

继续理解这幅图

img

给了一个skel对象

image-20220405224617985

和前面差不多创建了一个Target

image-20220405224710000

然后再一路跟下去

image-20220405225713166

image-20220405225758562

image-20220405225817467

我们直接看objTable

image-20220405225923363

默认创建一个分布式垃圾回收

image-20220405230022252

远程服务的对象

image-20220405230148895

创建注册中心的对象

image-20220405230214441

绑定

image-20220405230432299

然后就是一个绑定过程

image-20220405230520526

可以看到put的值

image-20220405230550215

客户端请求注册中心

我这边两边改了下端口

image-20220405232636396

liveRef

这边就是直接本地封装了一下

image-20220405233255005

这边又调了一个createProxy,和前面流程是一样的

image-20220405233903073

可以看又注册了一个

image-20220405233933386

下一步是获取远程对象

创建一个远程连接

image-20220406104217882

image-20220406104520142

调了call.executeCall()

image-20220406104544344

然后再看上层的逻辑,用了一个反序列化读取,var22是动态代理,这是一个反序列化的点

image-20220406104641065

还要一个点就是,如果是一个异常的类,然后就会客户端进行一个反序列化,所以说只要调用了invoke就可能被攻击

image-20220406105209388

最后的结果就是

image-20220406105410901

客户端请求服务端

我们继续调试,一路上到了invoke

image-20220406105715229

image-20220406110026858

和前面差不多,再跟下去

image-20220406110115539

然后看marshalValue

image-20220406110206297

基本上就是一个序列化的逻辑

image-20220406110329627

最后看到还是调用了call.executeCall(),也就是我们前面看到的反序列化的点还是存在

image-20220406110440630

unmarshalValue

image-20220406110848080

可以看到这里也可能有一个反序列化的点

image-20220406110928008

可以看到值被反序列化了

image-20220406111035598

客户端请求注册中心

这边直接走到listen

image-20220406121853558

我们分析的是客户端请求服务端来调试

image-20220406121954140

image-20220406122117283

image-20220406122137845

image-20220406122208936

可以看到已经开始解析字段了

image-20220406122246452

image-20220406122453654

image-20220406122536535

可以看到会获取ObjectTabletarget

image-20220406122612781

直接下个断点,然后客户端请求

image-20220406122636728

可以看到直接断到了断点

image-20220406122828370

我们可以看到target的值

image-20220406122923197

获取分发器

image-20220406123030053

一路跟下去

image-20220406123540034

image-20220406123623134

最后到了

image-20220406123746022

image-20220406123758738

可以分析dispatch

传的是2

image-20220406124216394

所以是调用case2

image-20220406124300477

可以看到有一个反序列化点,反序列化读出了,所以这是一个可以攻击的点

image-20220406124459160

客户端处理stub,服务端处理skel

img

客户端请求服务端-服务端

这边会继续调传target

image-20220406131426057

再一路跟下去,不同的就是skel为null

image-20220406131541165

继续可以看到获取了我们定义的远程方法

image-20220406131632888

可以看到和前面一样,会把传参反序列化出来

image-20220406131726170

可以看到做了一个反序列化

可以看到传入的参数和进行了远程调用

image-20220406132206580

最后服务端把返回值序列化传输

image-20220406132324465

客户端请求服务端-DGC(RMI分布式垃圾回收)

image-20220406171636723

下个断点

image-20220406171558276

可以看到和前面创建代理的时候差不多

image-20220406171854493

还是创建了一个类stub

image-20220406172032710

然后再put到了target

image-20220406172117574

我们看DGCImpl_Stub

可以看到都调用了invoke,前面我们知道invoke里面是有反序列化的点的image-20220406172253555

我们再看DGC的服务端,也有反序列化点,也就是说都是可以被攻击的

image-20220406172521489