java渗透测试基础之——RMI

本文介绍了Java的RMI(远程方法调用)机制,包括其基本概念、涉及的主体、主要API以及一个服务端和客户端的示例。RMI允许不同JVM间的对象调用,要求对象可序列化。文章还详细阐述了RMI的逻辑关系,强调了Stub在远程调用中的作用,以及如何通过RMIRegistry获取和使用Stub。
摘要由CSDN通过智能技术生成

一、概述

RMI全称是Remote Method Invocation(远程方法调用),是专为Java环境设计的远程方法调用机制,远程服务器提供API,客户端根据API提供相应参数即可调用远程方法。

由此可见,使用RMI时会涉及到参数传递和结果返回,参数为对象时,要求对象可以被序列化。目的是为了让两个隔离的java虚拟机,如虚拟机A能够调用到虚拟机B中的对象,而且这些虚拟机可以不存在于同一台主机上。

RMI存在着三个主体:

  • RMI Registry

  • RMI Client

  • RMI Server

RMI中主要的api大致有:

  • java.rmi:提供客户端需要的类、接口和异常;

  • java.rmi.server:提供服务端需要的类、接口和异常;

  • java.rmi.registry:提供注册表的创建以及查找和命名远程对象的类、接口和异常;

二、举例

1、服务端

就服务端而言,需要提供远程对象给与客户端远程调用,所谓远程对象即实现java.rmi.Remote接口的类或者继承了java.rmi.Remote接口的所有接口的远程对象。

一个RMI Server分为三部分:

  • 部分1:一个继承了java.rmi.Remote 的接口,其中定义我们要远程调用的函数,比如这里的sayHello()

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

public interface RemoteObj extends Remote {
    public String sayHello(String keywords) throws Exception;
}
  • 部分2:一个实现了此接口的类,首先有几个关键点:

    • 实现方法必须抛出RemoteException异常

    • 实现类需要同时继承UnicastRemoteObject类,如果不继承,则需要手工初始化远程对象,在远程对象的构造方法的调用UnicastRemoteObject.exportObject()静态方法

    • 只有在接口中声明的方法才能被调用到

import java.rmi.server.UnicastRemoteObject;

public class RemoteObjImpl extends UnicastRemoteObject implements RemoteObj {
    public RemoteObjImpl() throws Exception{
    }
    
    public String sayHello(String keywords) throws Exception {
        String upKeywrods = keywords.toUpperCase();
        System.out.println("Server:" + upKeywrods);
        return upKeywrods;
    }
}
  • 部分3:一个主类,用来创建Registry,并将上面的类实例化后绑定到一个地址。就服务端而言其实现的关键在于Naming这个类,利用bind方法将对象绑定一个名。

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIServer {
    public static void main(String[] args) throws Exception {
        RemoteObjImpl remoteObj = new RemoteObjImpl();
        Registry registry = LocateRegistry.createRegistry(1099);
//         写法一:通过Naming绑定
//        Naming.bind("remoteObj",remoteObj);
//         写法二:通过registry绑定
        registry.bind("remoteObj",remoteObj);
    }
}

2、客户端

客户端也需要具有接口类

import java.rmi.Remote;

public interface RemoteObj extends Remote {
    public String sayHello(String keywords) throws Exception;
}

客户端的操作可变性就很多了,同样是通过Naming类中提供的方法来操作,有如下几种方法:

  • lookup

  • list

  • bind

  • rebind

  • unbind

拿list举例子(可以列出目标上所有绑定的对象):

  • 我们可以通过list方法获取到目标server上有哪些绑定的方法

import java.rmi.Naming;

public class HelloRmiClientList {
    public static void main(String[] args) throws Exception {
        String[] clazz = Naming.list("rmi://127.0.0.1:1099");
        for (String s:clazz) {
            System.out.println(s);
        }
    }
}

用lookup举例子(获得某个远程对象):

  • 当上面获取到接口的方法时,用lookup可以执行远程方法,代码是在远程服务器器上执行,返回给客户端的为一个Remote对象,可以强转为我们本地的对象进行利用:

  • 所以捋一捋这整个过程,首先客户端连接Registry,并在其中寻找Name是Hello的对象,这个对应数据流中的Call消息;然后Registry返回一个序列化的数据,这个就是找到的Name=Hello的对象,这个对应数据流中的ReturnData消息;客户端反序列化该对象,发现该对象是一个远程对象,地址在192.168.135.142:33769 ,于是再与这个地址建立TCP连接;在这个新的连接中,才执行真正远程方法调用,也就是hello() 。

import java.rmi.Naming;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;

public class RMIClient {
    public static void main(String[] args) throws Exception {
//        方法一:通过registry的lookup
//        Registry registry = LocateRegistry.getRegistry("127.0.0.1", 1099);
//        RemoteObj remoteObj = (RemoteObj)registry.lookup("remoteObj");
//        System.out.println("Client:" + remoteObj.sayHello("hello"));
//        方法二:通过Naming的lookup
        RemoteObj RemoteObj = (RemoteObj)Naming.lookup("rmi://127.0.0.1:1099/remoteObj");
        System.out.println("Client:" + RemoteObj.sayHello("hello"));
    }
}

Server的窗口:

Client的窗口:

在上述操作后,我们会发现Server输出了Server:HELLO,并且Client端也输出了Client:HELLO。到底怎么回事呢?

其实,在Server启动的时候,Server启动了一个RMI的注册中心,接着把RemoteObjImpl暴露并注册到RMI注册中心,其中存储着RemoteObjImpl的stub数据,包含有RemoteObjImpl所在服务器的ip和port。在Client启动之后,通过连接RMI注册中心,并从其中根据名称查询到了对应的对象(JNDI),并把其数据下载到本地,然后RMI会根据stub存储的信息,也就是Server中RemoteObjImpl实现暴露的ip和port,最后通过JRMP协议发起RMI请求,RMI后,Server输出大写之后的hello并通过JRMP协议把大写之后的hello的序列化数据返回给程序B,程序B对其反序列化后输出。

PS:java.rmi.registry.Registry 和 java.rmi.Naming 类之间的区别

不同之处在于Naming是一个具有静态方法的实用程序类,Registry而是一个远程接口。

请注意,传递给java.rmi.Naming中name的参数是 URL 格式,并包括注册表的位置,而使用java.rmi.registry.Registry,name只是名称。

例如,可以这样调用:

Naming.rebind("//host/objName", myObj);

而使用Registry,您需要注册表对象上的现有句柄,并且调用:

Registry registry = LocateRegistry.getRegistry("host");

registry.rebind("objName", myObj);

所以Naming实际上只是一个方便的类,它可以让您不必Registry手动查找 - 它一步执行注册表查找和重新绑定。

三、逻辑关系

1、基本逻辑关系

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

在JVM之间通信时,RMI对远程对象和非远程对象的处理方式是不一样的,它并没有直接把远程对象复制一份传递给客户端,而是传递了一个远程对象的Stub,Stub基本上相当于是远程对象的引用或者代理。Stub对开发者是透明的,客户端可以像调用本地方法一样直接通过它来调用远程方法。Stub中包含了远程对象的定位信息,如Socket端口、服务端主机地址等等,并实现了远程调用过程中具体的底层网络通信细节,所以RMI远程调用逻辑是这样的:

从逻辑上来看,数据是在Client和Server之间横向流动的,但是实际上是从Client到Stub,然后从Skeleton到Server这样纵向流动的。

  • 1.Server端监听一个端口,这个端口是JVM随机选择的;

  • 2.Client端并不知道Server远程对象的通信地址和端口,但是Stub中包含了这些信息,并封装了底层网络操作;

  • 3.Client端可以调用Stub上的方法;

  • 4.Stub连接到Server端监听的通信端口并提交参数;

  • 5.远程Server端上执行具体的方法,并返回结果给Stub;

  • 6.Stub返回执行结果给Client端,从Client看来就好像是Stub在本地执行了这个方法一样;

2、Stub如何获取呢

Stub的获取方式有很多,常见的方法是调用某个远程服务上的方法,向远程服务获取存根。但是调用远程方法又必须先有远程对象的Stub,所以这里有个死循环问题。JDK提供了一个RMI注册表(RMIRegistry)来解决这个问题。RMIRegistry也是一个远程对象,默认监听在传说中的1099端口上,可以使用代码启动RMIRegistry,也可以使用rmiregistry命令。

  • 要注册远程对象,需要RMI URL和一个远程对象的引用

IHello rhello = newHelloImpl();
LocateRegistry.createRegistry(1099);
Naming.bind("rmi://0.0.0.0:1099/hello", rhello);

LocateRegistry.getRegistry()会使用给定的主机和端口等信息本地创建一个Stub对象作为Registry远程对象的代理,从而启动整个远程调用逻辑。服务端应用程序可以向RMI注册表中注册远程对象,然后客户端向RMI注册表查询某个远程对象名称,来获取该远程对象的Stub。

Registry registry = LocateRegistry.getRegistry("kingx_kali_host",1099);
IHello rhello = (IHello) registry.lookup("hello");
rhello.sayHello("test");
  • 使用RMI Registry之后,RMI的调用关系是这样的:

所以其实从客户端角度看,服务端应用是有两个端口的,一个是RMI Registry端口(默认为1099),另一个是远程对象的通信端口(随机分配的)。这个通信细节比较重要,真实利用过程中可能会在这里遇到一些坑。

参考:

【技术分享】一文回顾攻击Java RMI方式

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Thunderclap_

点赞、关注加收藏~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值