JAVA RMI反序列化知识详解

一、前言

在Java反序列化漏洞挖掘或利用的时候经常会遇见RMI,本文会讲述什么是RMI、RMI攻击方法、JEP290限制、绕过JEP290限制。

二、RMI简介

JAVA本身提供了一种RPC框架 RMI及Java 远程方法调用(Java Remote Method Invocation),可以在不同的Java 虚拟机之间进行对象间的通讯,RMI是基于JRMP协议(Java Remote Message Protocol Java远程消息交换协议)去实现的。

RMI调用逻辑

3

image-20200506223648108

RMI主要分为三部分

  • RMI Registry注册中心
  • RMI Client 客户端
  • RMI Server服务端

三、RMI的实现

注册中心代码

创建一个继承java.rmi.Remote的接口

创建注册中心代码

服务端代码

先创建一个继承java.rmi.Remote的接口

继承UnicastRemoteObject类,实现上面的接口

写服务端的启动类,用于创建远程对象注册表和注册远程对象

客户端代码

创建接口类

连接注册服务 查找hello对象

启动服务端之后,在启动客户端看下.

服务端输出了

1

客户端输出了2

四、攻击方法

服务端攻击注册中心

从第一张图可以看到服务端也是向注册中心序列化传输远程对象,那么直接把远程对象改成反序列化Gadget看下

修改服务端代码

在服务端执行这段代码 注册中心计算器会弹出,这段代码就是ysoserial工具的RMIRegistryExploit代码,debug看下注册中心执行过程

触发反序列化操作位置

sun.rmi.registry.RegistryImpl_Skel#dispatch

4

调用栈

注册中心攻击客户端

首先借助ysoserial项目启动一个JRMP服务端执行命令

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 "open /Applications/Calculator.app"

然后直接启动上面客户端的代码,会发现计算器直接被弹出,debug看下客户端代码

代码位置sun.rmi.registry.RegistryImpl_Stub#lookup

5

90行调用newCall方法创建socket连接,94行序列化lookup参数,104行反序列化返回值,而此时Registry的返回值是CommonsCollections5的调用链,所以这里直接反序列化就会触发.

客户端攻击注册中心

1.直接启动上面的注册中心代码

2.借助ysoserial项目JRMPClient攻击注册中心命令

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPClient 192.168.102.1 1099 CommonsCollections5 "open /Applications/Calculator.app"

执行完命令后计算器直接弹出来了,原因是RMI框架采用DGC(Distributed Garbage Collection)分布式垃圾收集机制来管理远程对象的生命周期,可以通过与DGC通信的方式发送恶意payload让注册中心反序列化。

debug注册中心代码看下。

sun.rmi.transport.DGCImpl_Skel#dispatch

6

可以看到这里进行了反序列化操作。

列下调用栈

JEP290

JDK6u141JDK7u131JDK 8u121加入了JEP 290限制,JEP 290过滤策略有

进程级过滤器

可以将进程级序列化过滤器作为命令行参数(“-Djdk.serialFilter =”)传递,或将其设置为$JAVA_HOME/conf/security/java.security中的系统属性。

自定义过滤器

可以使用自定义过滤器来重写特定流的进程级过滤器

内置过滤器

JDK分别为RMI注册表和RMI分布式垃圾收集器提供了相应的内置过滤器。这两个过滤器都配置为白名单,即只允许反序列化特定类。

这里我把jdk版本换成jdk1.8.0_181,默认使用内置过滤器。然后直接使用上面的服务端攻击注册中心poc看下,执行完RMI Registry会提示这样的一个错误:

信息: ObjectInputFilter REJECTED: class sun.reflect.annotation.AnnotationInvocationHandler, array length: -1, nRefs: 8, depth: 2, bytes: 285, ex: n/a

debug看下

sun.rmi.registry.RegistryImpl#registryFilter

白名单列表:

  • String.class
  • Number.class
  • Remote.class
  • Proxy.class
  • UnicastRef.class
  • RMIClientSocketFactory.class
  • RMIServerSocketFactory.class
  • ActivationID.class
  • UID.class

调用栈

UnicastRef对象

用UnicastRef对象新建一个RMI连接绕过JEP290的限制,看下ysoserial的JRMPClient的payload

7

这几行代码会向指定的RMI Registry发起请求,并且在白名单列表里面,在看下服务端和客户端调用。LocateRegistry.getRegistry方法的代码。

代码位置java.rmi.registry#getRegistry

8

和payload发起RMI Registry请求代码是一样的。

先用ysoserial启动RMI registryjava -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.JRMPListener 1099 CommonsCollections5 "open /Applications/Calculator.app"

然后把这个payload放在服务端bind看下

在服务端执行RMI registry的计算器就弹出来了,debug RMI registry代码看下.

调用栈

原理就是利用在白名单的UnicastRef类来发起一个RMI连接,在高版本jdk下ysoserial的JRMPListener依然可以利用.

用Object绕JEP290限制

JEP290只是为RMI注册表和RMI分布式垃圾收集器提供了相应的内置过滤器,在RMI客户端和服务端在通信时参数传递这块是没有做处理的,而参数传递也是基于序列化数据传输,那么如果参数是泛型的payload,传输依然会有问题。

先把接口都新增一个sayPayload的方法,参数都是Object类型的

在把服务端HelloImpl代码改下,去实现这个方法。

客户端在调用这个sayPayload方法时直接传payload看下

执行后服务端计算器直接弹出,如果把这个payload作为sayPayload方法的返回值 客户端计算器也会弹出。

看下反序列化的地方

sun.rmi.server.UnicastRef#marshalValue

9

调用栈

在实际使用场景很少有参数是Object类型的,而攻击者可以完全操作客户端,因此可以用恶意对象替换从Object类派生的参数(例如String),具体有如下四种bypass的思路

  • 将java.rmi包的代码复制到新包,并在新包中修改相应的代码
  • 将调试器附加到正在运行的客户端,并在序列化之前替换这些对象
  • 使用诸如Javassist这样的工具修改字节码
  • 通过实现代理替换网络流上已经序列化的对象

我这里使用第三个方法,由afanti师傅实现的通过RASP hook住java.rmi.server.RemoteObjectInvocationHandler类的InvokeRemoteMethod方法的第三个参数非Object的改为Object的gadget。不熟悉RASP的先要去了解下。

我这里使用CommonsCollections5这条链,Hook invokeRemoteMethod函数。

10

客户端代码还是不变

VM options参数填写rasp jar对应的地址11

然后直接运行
12

控制台会抛出一个错误 随后计算器也直接弹出来了.

debug看下可以看到

java.rmi.server.RemoteObjectInvocationHandler#invokeRemoteMethod这里args参数的值已经修改为CommonsCollections5的gadget了.13

五、总结

RMI数据传输都是基于序列化数据传输,RMI Registry、Client、Server都能相互攻击,在你攻击别人的时候 可能也会被人攻击。

参考链接

https://www.anquanke.com/post/id/200860#h2-3

https://xz.aliyun.com/t/7264#toc-2

https://mogwailabs.de/blog/2019/03/attacking-java-rmi-services-after-jep-290/

https://kingx.me/Exploit-Java-Deserialization-with-RMI.html

Written by