首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >初探java安全之RMI(1)

初探java安全之RMI(1)

作者头像
pankas
发布2022-10-30 11:48:27
3780
发布2022-10-30 11:48:27
举报

RMI 介绍

RMI全称是 Remote Method Invocation,远程方法调用。从这个名字就可以看出,他的目标和RPC其实 是类似的,是让某个Java虚拟机上的对象调⽤另⼀个Java虚拟机中对象上的方法,只不过RMI是Java独 有的⼀种机制。

RMI 服务演示

示意图

the RMI system, using an existing web server, communicates from serve to client and from client to server
the RMI system, using an existing web server, communicates from serve to client and from client to server

一个简单的RMI服务可以由一下内容组成。

  1. 定义继承 java.rmi.Remote 的接口
  2. 定义实现上述接口的类
  3. 创建服务端将 远程对象的类 注册到 registry 中并绑定到一个地址
  4. 创建客户端连接远程 RMI 服务,到的对应实现接口的类对象

通过 RMI 远程执行的方法还是执行在 RMI 服务器上的,客户端接收方法执行的返回结果。

本地搭建演示

image-20221012184645850
image-20221012184645850

定义了三个包 clientregistryserver

registry 包

这个包里定义了一个接口和实现这个接口的类,这个接口要求 client 和 server都要导入, client 通过 rmi 服务得到的对象返回的就是实现了改接口的类对象。

RemoteMethod.java

package registry;

import java.rmi.Remote;
import java.rmi.RemoteException;

//定义接口,该接口必须继承 java.rmi.Remote 接口
public interface RemoteMethod extends Remote {
    //自定义要远程调用的方法,这些方法必须有抛出 RemoteException 异常
    public String sayHello() throws RemoteException;
    public int calcAdd(int num1, int num2) throws RemoteException;
}

RemoteObj.java

package registry;

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

//要远程实现的类,该类要实现对应的接口,并且要继承 UnicastRemoteObj 类
public class RemoteObj extends UnicastRemoteObject implements RemoteMethod {
    public RemoteObj() throws RemoteException {
        super();
    }

    @Override
    public String sayHello() throws RemoteException {
        return "hello RMI";
    }

    @Override
    public int calcAdd(int num1, int num2) throws RemoteException {
        return num1 + num2;
    }
}
server 包

该包用于创建 RMI 服务器,并将 registry 包中的类对象注册到 registry 仓库中,使得客户端能够调用该对象实现的接口中对应的方法。

CreateRmiServer.java

package server;

import registry.RemoteObj;
import java.rmi.Naming;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;

public class CreateRmiServer {
    RemoteObj obj;
    int port;
    public CreateRmiServer(int port) throws RemoteException {
        //实例化远程对象
        this.obj = new RemoteObj();
        this.port = port;
    }
    public void start() throws Exception {
        //创建 registry 并绑定端口
        LocateRegistry.createRegistry(this.port);
        //将实例化的远程对象绑定到一个地址
        Naming.bind("rmi://127.0.0.1:" + this.port + "/execMethod", this.obj);
        System.out.println("Run in port " + this.port);
    }
}

Server.java

RMI 服务器主方法,用于启动

package server;

public class Server {
    public static void main(String[] args) throws Exception {
        new CreateRmiServer(1099).start();
    }
}
client 包

创建客户端用于访问 RMI 服务器

CreateClient.java

客户端要导入 要调用的方法的接口,通过 RMI 服务器远程得到的对象是实现了这个接口的对象,这时接口的重要性就体现出来了,告诉客户端你可以调用的方法有哪些,从而正常调用方法。

package client;

import registry.RemoteMethod;
import registry.RemoteObj;
import java.rmi.Naming;

public class CreateClient {
    int port;
    public CreateClient(int port) {
        this.port = port;
    }
    public RemoteMethod getObj() throws Exception {
        //返回一个 实现了对应接口的对象 (接口中对应着一些方法)
        return (RemoteMethod) Naming.lookup("rmi://127.0.0.1:" + this.port + "/execMethod");
    }
}

Client.java

package client;

import registry.RemoteMethod;

public class Client {
    public static void main(String[] args) throws Exception {
        //创建客户端对象
        CreateClient client = new CreateClient(1099);
        //获得远程对象
        RemoteMethod obj = client.getObj();
        //调用远程方法执行
        String hello = obj.sayHello();
        int addRes = obj.calcAdd(1, 1);
        System.out.println("this is client");
        System.out.println(hello);
        System.out.println(addRes);
    }
}

编译运行 Server.javaClient.java

服务端

image-20221012190835663
image-20221012190835663

客户端

image-20221012190910555
image-20221012190910555

成功调用 sayHellocalcAdd 方法

RMI通信过程分析

使用 wireshark 抓下包

image-20221012195712118
image-20221012195712118

这是完整的通信过程,我们可以发现,整个过程进行了两次TCP握手,也就是我们实际建立了两次 TCP连接。

第⼀次建立TCP连接是连接远端 127.0.0.11099 端口 ,就是我们在代码里看到的端口,⼆ 者进行沟通后,客户端向远端发送了⼀个 “Call” 消息,远端回复了⼀个 “ReturnData” 消息,然后客户端新建了⼀ 个TCP连接,连到远端的 2802 端口。

为什么客户端会连接33769端口呢?

细细阅读数据包我们会发现,在”ReturnData”这个包中,返回了目标的IP地址 172.28.128.1 ,其 后跟的⼀个字节 \x00\x00\x0a\xf2 ,刚好就是整数 2802 的网络序列:

0000   02 00 00 00 45 00 01 65 b9 a3 40 00 80 06 00 00   ....E..e..@.....
0010   7f 00 00 01 7f 00 00 01 04 4b 18 d1 76 8f 8c d5   .........K..v...
0020   6b cd 58 cb 50 18 20 0f d3 98 00 00 51 ac ed 00   k.X.P. .....Q...
0030   05 77 0f 01 94 17 4f 3e 00 00 01 83 cb e7 b7 f5   .w....O>........
0040   80 11 73 7d 00 00 00 02 00 0f 6a 61 76 61 2e 72   ..s}......java.r
0050   6d 69 2e 52 65 6d 6f 74 65 00 15 72 65 67 69 73   mi.Remote..regis
0060   74 72 79 2e 52 65 6d 6f 74 65 4d 65 74 68 6f 64   try.RemoteMethod
0070   70 78 72 00 17 6a 61 76 61 2e 6c 61 6e 67 2e 72   pxr..java.lang.r
0080   65 66 6c 65 63 74 2e 50 72 6f 78 79 e1 27 da 20   eflect.Proxy.'. 
0090   cc 10 43 cb 02 00 01 4c 00 01 68 74 00 25 4c 6a   ..C....L..ht.%Lj
00a0   61 76 61 2f 6c 61 6e 67 2f 72 65 66 6c 65 63 74   ava/lang/reflect
00b0   2f 49 6e 76 6f 63 61 74 69 6f 6e 48 61 6e 64 6c   /InvocationHandl
00c0   65 72 3b 70 78 70 73 72 00 2d 6a 61 76 61 2e 72   er;pxpsr.-java.r
00d0   6d 69 2e 73 65 72 76 65 72 2e 52 65 6d 6f 74 65   mi.server.Remote
00e0   4f 62 6a 65 63 74 49 6e 76 6f 63 61 74 69 6f 6e   ObjectInvocation
00f0   48 61 6e 64 6c 65 72 00 00 00 00 00 00 00 02 02   Handler.........
0100   00 00 70 78 72 00 1c 6a 61 76 61 2e 72 6d 69 2e   ..pxr..java.rmi.
0110   73 65 72 76 65 72 2e 52 65 6d 6f 74 65 4f 62 6a   server.RemoteObj
0120   65 63 74 d3 61 b4 91 0c 61 33 1e 03 00 00 70 78   ect.a...a3....px
0130   70 77 35 00 0a 55 6e 69 63 61 73 74 52 65 66 00   pw5..UnicastRef.
0140   0c 31 37 32 2e 32 38 2e 31 32 38 2e 31 00 00 0a   .172.28.128.1...
0150   f2 be 5d ce 36 ed b5 55 22 94 17 4f 3e 00 00 01   ..].6..U"..O>...
0160   83 cb e7 b7 f5 80 01 01 78                        ........x
image-20221012200130413
image-20221012200130413

同时分析这个数据包不难发现这是 java 反序列化的数据,从 \xAC\xED 开始往后就是 java 反序列化的数据了,IP和端口是这个对象的⼀部分。

所以整个过程,就是客户端先连接Registry,并在其中寻找Name是execMethod的对象,这个对应数据流中的Call消息

image-20221012202651543
image-20221012202651543

然后Registry返回⼀个序列化的数据,这个就是找到的 RemoteMethod 的对象,这个对应 数据流中的ReturnData消息;客户端反序列化该对象,发现该对象是⼀个远程对象,地址在 172.28.128.1:2802 ,于是再与这个地址建立TCP连接;在这个新的连接中,才执行真正远程方法调用,也就是执行 sayHello()

关系图如下:

image-20221012203014485
image-20221012203014485

RMI Registry就像⼀个网关,他自己是不会执行远程方法的,但RMI Server可以在上⾯注册⼀个Name 到对象的绑定关系;RMI Client通过Name向RMI Registry查询,得到这个绑定关系,然后再连接RMI Server;最后,远程方法实际上在RMI Server上调用。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-10-11,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • RMI 介绍
  • RMI 服务演示
  • 本地搭建演示
    • registry 包
      • server 包
        • client 包
        • RMI通信过程分析
        相关产品与服务
        文件存储
        文件存储(Cloud File Storage,CFS)为您提供安全可靠、可扩展的共享文件存储服务。文件存储可与腾讯云服务器、容器服务、批量计算等服务搭配使用,为多个计算节点提供容量和性能可弹性扩展的高性能共享存储。腾讯云文件存储的管理界面简单、易使用,可实现对现有应用的无缝集成;按实际用量付费,为您节约成本,简化 IT 运维工作。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档