目录

  1. 1. 前言
  2. 2. 概念
  3. 3. 创建
    1. 3.1. 服务端
    2. 3.2. 客户端
  4. 4. Attack
    1. 4.1. 攻击RMI Registry
    2. 4.2. RMI利用codebase执行任意代码
  5. 5. 调试

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

RMI

2023/12/7 Web Java
  |     |   总文章阅读量:

前言

RMI,全称Remote Method Invocation,远程方法调用

RMI有客户端和服务端,在Java中客户端可以通过RMI调用来服务端的方法,危险程度无需多言

参考文章:

负数零:https://fushuling.com/index.php/2023/01/30/java%e5%ae%89%e5%85%a8%e7%ac%94%e8%ae%b0/

Boogipop:https://boogipop.com/2023/03/02/%E6%B5%85%E5%AD%A6RMI%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96

以及p神的《java安全漫谈》


概念

流程图如下:

image-20231207223316686

服务端注册了RMI后会在RMI Registry进行注册,之后客户端调用方法都是直接从这个注册中心取出

RMI分为三个主体部分:

  • Client-客户端:客户端调用服务端的方法

  • Server-服务端:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果

  • Registry-注册中心:其实本质就是一个网关/map,相当于是字典一样,用于客户端查询要调用的方法的引用

    Registry自己不会执行远程方法,但它可以通过RMI Server在上面注册一个Name到对象的绑定关系

    然后用RMI Client通过Name向RMI Registry查询,得到这个绑定关系,最后再连接RMI Server

    因此,远程方法实际上是在RMI Server上调用的

注:在低版本的JDK中,Server与Registry是可以不在一台服务器上的,而在高版本的JDK中,Server与Registry只能在一台服务器上,否则无法注册成功


创建

服务端

先简单起个RMI服务

package com.RMI;

import java.rmi.Naming;
import java.rmi.Remote;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.rmi.server.UnicastRemoteObject;

public class RMIServer {
    public interface IRemoteHelloWorld extends Remote {
        public String hello() throws RemoteException;
    }

    public class RemoteHelloWorld extends UnicastRemoteObject implements
            IRemoteHelloWorld {
        protected RemoteHelloWorld() throws RemoteException {
            super();
        }

        public String hello() throws RemoteException {
            System.out.println("call from");
            return "Hello world";
        }
    }

    private void start() throws Exception {
        RemoteHelloWorld h = new RemoteHelloWorld();
        LocateRegistry.createRegistry(1099);
        Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
    }

    public static void main(String[] args) throws Exception {
        new RMIServer().start();
    }
}

这里的Server其实包含了Registry和Server两部分

RMI Registry部分:

LocateRegistry.createRegistry(1099);
Naming.bind("rmi://127.0.0.1:1099/Hello", new RemoteHelloWorld());

第一行创建并运行RMI Registry,第二行将RemoteHelloWorld对象绑定到Hello这个名字上

注:如果RMI Registry在本地运行,那么host和port是可以省略的,此时host默认是localhost ,port默认是1099

Naming.bind("Hello", new RemoteHelloWorld());

而RMI Server分为三部分:

  • 继承了java.rmi.Remote的接口,定义了我们远程调用的函数,如这里的hello()

    public interface IRemoteHelloWorld extends Remote {
        public String hello() throws RemoteException;
    }
  • 一个实现了此接口的类

    public class RemoteHelloWorld extends UnicastRemoteObject implements
            IRemoteHelloWorld {
        protected RemoteHelloWorld() throws RemoteException {
            super();
        }
  • 一个主类,用来创建Registry,并将上面的类实例例化后绑定到一个地址。这就是我们所谓的Server了(这里的代码还包含了RMI Registry部分)

    private void start() throws Exception {
        RemoteHelloWorld h = new RemoteHelloWorld();
        LocateRegistry.createRegistry(1099);
        Naming.rebind("rmi://127.0.0.1:1099/Hello", h);
    }
      
    public static void main(String[] args) throws Exception {
        new RMIServer().start();
    }

客户端

package com.RMI;

import com.RMI.RMIServer;
import java.rmi.Naming;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;

public class TrainMain {
    public static void main(String[] args) throws Exception {
        RMIServer.IRemoteHelloWorld hello = (RMIServer.IRemoteHelloWorld) Naming
                .lookup("rmi://127.0.0.1:1099/Hello");
        String ret = hello.hello();
        System.out.println(ret);
    }
}

客户端就比服务端简单多了,使用Naming.lookup在Registry中寻找到名字是Hello的对象,后面的操作和本地操作一致。

现在我们先运行服务端,再运行客户端

image-20231207231649072

image-20231207231441252

可以看到服务端打印了call from,客户端远程调用了 hello()方法打印出了Hello world


Attack

攻击RMI Registry

我们已经知道RMI Registry可以管理远程对象,类似于远程对象的“后台”

那么我们能否直接访问这个“后台”,然后修改远程服务器上Hello对应的对象呢?

答案是不行的,Java对远程访问RMI Registry做了限制,只有来源地址是localhost的时候,才能调用rebind、bind、unbind等方法,直接调用的话会报错

不过list和lookup方法可以远程调用,

list方法可以列出目标上所有绑定的对象

String[] s = Naming.list("rmi://127.0.0.1:1099");
System.out.println(s);

image-20231208001139398

而lookup作用就是获得某个远程对象,在这里客户端的代码中一开始就使用了lookup获取了hello对象

那么,只要目标服务器上存在一些危险方法,我们通过RMI就可以对其进行调用
工具:https://github.com/NickstaDB/BaRMIe

其中一个功能就是进行危险方法的探测


RMI利用codebase执行任意代码

这个东西有点古老,可以追溯到Java能运行在浏览器中的时候,暂时先不学这个


调试

先参考pop✌的博客

调试过程中发现RMI发送、接收的数据都是反序列化数据

等完善了反序列化再回来看