RMI攻击

之前在关于Java的小点中介绍了一些Java安全入门所需要掌握的基本知识,那么这里我们就要来看看具体的对RMI进行攻击的方法

首先要知道什么是RMI

简介

RMI(Remote Method Invocation),远程方法调用。跟RPC差不多,是java独立实现的一种机制。实际上就是在一个java虚拟机上调用另一个java虚拟机的对象上的方法。

RMI依赖的通信协议为JRMP(Java Remote Message Protocol ,Java 远程消息交换协议),该协议为Java定制,要求服务端与客户端都为Java编写。这个协议就像HTTP协议一样,规定了客户端和服务端通信要满足的规范。(我们可以再之后数据包中看到该协议特征)

在RMI中对象是通过序列化方式进行编码传输的。(我们将在之后证实)

RMI分为三个主体部分:

  • Client-客户端:客户端调用服务端的方法
  • Server-服务端:远程调用方法对象的提供者,也是代码真正执行的地方,执行结束会返回给客户端一个方法执行的结果。
  • Registry-注册中心:其实本质就是一个map,相当于是字典一样,用于客户端查询要调用的方法的引用。

总体RMI的调用实现目的就是调用远程机器的类跟调用一个写在自己的本地的类一样。

唯一区别就是RMI服务端提供的方法,被调用的时候该方法是执行在服务端

RMI关键点在于,所有的方法执行都是在服务端上执行的,这时肯定会有人有疑问,为什么在服务端进行调用还会导致RCE咧?我们暂且按下不表,后面在看具体漏洞就知道为什么了

RMI调用流程

Server部署:

  1. Server向Registry注册远程对象,远程对象绑定在一个//host:port/objectname上,形成一个映射表(Service-Stub)。

Client调用:

  1. Client向Registry通过RMI地址查询对应的远程引用(Stub)。这个远程引用包含了一个服务器主机名和端口号。
  2. Client拿着Registry给它的远程引用,照着上面的服务器主机名、端口去连接提供服务的远程RMI服务器
  3. Client传送给Server需要调用函数的输入参数,Server执行远程方法,并返回给Client执行结果。

image-20220907013338336

看到整个调用过程我们发现所有的内容传递都是对象,过程自然是序列化与反序列化,那这是我们就会有疑问,如果在客户端并没有服务端所返回的对象类那不就不能存储我们的对象了吗?

这时JNDI就生效了,在JNDI远程进行加载的时候,会通过lookup来对类进行本地查找,如果本地找不到对应类的定义,就会去到server端预先定义好的codebase地址中(一般是http)获取对应类的class文件,并自动执行类的静态方法和getObjectInstance方法,那么我们只要构造恶意类文件,并且通过RMI返回对应类的实例化对象,就可以导致RCE,多说无益,我们上手试试

RCE示例

public interface HelloService extends Remote {
    String sayHello() throws RemoteException;
}
public class HelloServiceImpl extends UnicastRemoteObject implements HelloService {
    protected HelloServiceImpl() throws RemoteException {
    }

    @Override
    public String sayHello() throws RemoteException {
        System.out.println("hello!");
        return "hello!";
    }
}