【漏洞】- 攻击Java RMI的方式
2022-10-14 00:3:24 Author: 信安文摘(查看原文) 阅读量:17 收藏

前言

通过学习RMI(Remote Method Invocation) 远程方法调用的基本知识了解到,参与一次 RMI 调用的有三个角色,分别是 Server 端,Registry 端和 Client 端。严格意义上来讲,只有 Registry 端使用 Registry 的端

因为 Registry 端只负责查询和传递引用,真正的方法调用是不需要经过 Registry 端的,只不过注册服务的称之为 Server 端,使用服务的称之为 Client 端。

攻击Register端

1. Register端反序列化

在使用 Registry 时,首先由 Server 端向 Registry 端绑定服务对象,这个对象是一个 Server 端生成的动态代理类,Registry 端会反序列化这个类对象并存在自己的 RegistryImpl 的 bindings 中,以供后续的查询。所以如果我们是一个恶意的 Server 端,向 Registry 端输送了一个恶意的对象,在其反序列化时就可以触发恶意调用。

因为注册的类对象需要是 Remote 类型的,所以使用 AnnotationInvocationHandler 来代理 Remote 接口。

2. yso RMIRegistryExploit 分析

攻击演示

RMI server 服务启动,使用https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms/blob/master/java-rmi-server/src/main/java/com/longofo/javarmi/RMIServer.java开启一个简单的rmi register服务,Registry 端需要具有相应的依赖及相应 JDK 版本,这里添加commons-collections 3.1依赖,jdk版本为jdk1.7.0_80

启动rmi server:

使用yso发送payload:

java -cp ysoserial-0.0.6-SNAPSHOT-all.jar ysoserial.exploit.RMIRegistryExploit 127.0.0.1 9999 CommonsCollections7 calc

弹出计算器,命令执行payload完成。

源码分析

ysoserial.exploit.RMIRegistryExploit#main打上断点,main函数中获取命令行中的相关参数,并尝试连接rmi register,接着在ysoserial.exploit.RMIRegistryExploit#exploit中构造恶意对象并bind到远程register中,触发反序列化漏洞。

ObjectPayload payloadObj = payloadClass.newInstance();
Object payload = payloadObj.getObject(command);

这里使用的是cc7链,返回的恶意对象是Hashtable类型。rmi中 bind 的对象是需要是 Remote 类型的,所以在yso中使用 AnnotationInvocationHandler 来代理 Remote 接口。

Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap(name, payload), Remote.class);

Gadgets.createMap(name, payload)的作用是将恶意对象封装成一个hashmap,key为随机值,value为恶意对象(Hashtable类型)。

传入Gadgets.createMemoitizedProxy()的参数为封装的hashmap代理的接口(Remote类型)

createMemoizedInvocationHandler(map),实例化AnnotationInvocationHandler,传入Override.class封装恶意对象的hashmap

Proxy.newProxyInstance创建动态代理并返回。

3. ObjectInputFilter REJECTED 问题

JEP 290 介绍

一开始在本地测试的时候,使用的jdk版本是1.8.0_261,payload并没有执行成功,报错:ObjectInputFilter REJECTED

原因是高版本中RMIRegistryImpl.registryFilter()函数中限制了可反序列化类的类型。

JEP290 是 Java 底层为了缓解反序列化攻击提出的一种解决方案,影响版本如下:

  • java 9 及以上

  • JDK 6u141

  • JDK 7u131

  • JDK 8u121

JEP 290 主要提供了几个机制:

  • 提供了一种灵活的机制,将可反序列化的类从任意类限制为上下文相关的类(黑白名单);

  • 限制反序列化的调用深度和复杂度;

  • 为 RMI export 的对象设置了验证机制;

  • 提供一个全局过滤器,可以在 properties 或配置文件中进行配置。

rmi中绕过JEP 290 的限制

网上查到的大部分资料,都是使用RemoteObjectInvocationHandler来创建代理类。然而本地尝试时使用的是java 1.8.0_261,在jdk8u231jdk8u241进一步限制了,故本地使用RemoteObjectInvocationHandler进行绕过时,肯定未成功:ObjectInputFilter REJECTED: class javax.management.BadAttributeValueExpException

RMI:绕过JEP290——上:https://m0d9.me/2020/07/02/RMI%EF%BC%9A%E7%BB%95%E8%BF%87JEP290%E2%80%94%E2%80%94%E4%B8%8A/

RMI:绕过JEP290——中

RMI:绕过JEP290——下

这三篇文章详细讲解了绕过JEP 290限制的方法,包括jdk8u231jdk8u241的进一步限制。后续单独学习下。

攻击server端

server端与register端的区别在于,server是真正执行远程对象方法的地方。

要攻击server端,需要在一定程度上了解server端对象方法的具体实现细节:对象方法的参数是否为Object类型;还需要了解server端的安全策略:是否允许动态类加载等。

本文主要学习两种针对server端的攻击方式:

  1. 恶意服务参数:如果server端绑定对象的方法参数是Object类型,Client 端可以传给 Server 端任意的类对象作为传入的参数,直接造成反序列化漏洞。

  2. 动态类加载:如果client端传递的方法参数是server端绑定对象的方法参数类型的子类,那么server端需要从client端提供的java.rmi.server.codebaseURL去加载对应的类(恶意类)。

1. 恶意服务参数

在 Client 端获取到 Server 端创建的 Stub 后,会在本地调用这个 Stub 并传递参数,Stub 会序列化这个参数,并传递给 Server 端,Server 端会反序列化 Client 端传入的参数并进行调用,如果这个参数是 Object 类型的情况下,Client 端可以传给 Server 端任意的类,直接造成反序列化漏洞。

比如绑定对象的实现接口为:

public interface RemoteInterface extends Remote {
   public String sayGoodbye(Object name) throws RemoteException;
}

那么调用远程对象的sayGoodbye方法时,就可以传入恶意类对象作为参数。

2. 动态类加载

攻击场景

攻击的目标启动了一个RMI服务端,并且目标可以动态类加载。

利用条件

  1. 安全管理器:由于Java SecurityManager的限制,默认是不允许远程加载的,如果需要进行远程加载类,需要安装RMISecurityManager并且配置java.security.policy

  2. 自动加载远程类文件属性:属性 java.rmi.server.useCodebaseOnly 的值必须为false。但是从JDK 6u457u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前虚拟机的java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止虚拟机从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。

测试环境及分析

使用:https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms

启动remote-class/src/main/java/com/longofo/remoteclass/HttpServer在本地启动一个http服务托管类文件。

启动java-rmi-server/src/main/java/com/longofo/javarmi/RMIServer2.java启动一个存在漏洞的rmi server。

启动java-rmi-client/src/main/java/com/longofo/javarmi/RMIClient2.java,发送payload,server端接收数据造成命令执行。

RMIServer2中,绑定的对象为ServicesImpl,其方法ServicesImpl.sendMessage(Message msg)接收的参数为Message类型;

RMIClient2中,调用绑定的对象服务的sendMessage方法时,传入的参数是ExportObject1类型,public class ExportObject1 extends Message implements ObjectFactory, Serializable{}ExportObject1类继承Message类,传入的参数为绑定对象接收参数类型的子类,在server端自然找不到该类,就会从java.rmi.server.codebase 指定路径(上面启动的http服务)加载类文件。

无论 Server 端还是 Client 端,只要有一端配置了 java.rmi.server.codebase,这个属性都会跟随数据流在两端流动。

注意事项

在server端需要安装RMISecurityManager并且配置java.security.policy,源码中使用如下方式获取策略文件:

System.setProperty("java.security.policy", RMIServer.class.getClassLoader().getResource("java.policy").getFile());

我的项目路径中存在中文,导致无法正常获取策略文件。

解决办法,直接在d盘根目录创建策略文件java.policy

System.setProperty("java.security.policy", "D:/java.policy");

利用该漏洞需要server端允许加载远程类,即java.rmi.server.useCodebaseOnly的值为false。

直接打的jdk版本:< JDK 6u45、JDK 7u21

高版本jdk复现:设置属性 System.setProperty("java.rmi.server.useCodebaseOnly", "false");

攻击clinet端

要攻击client端,我们需要提前准备一个恶意的server端。当client端调用远程对象方法时,server返回恶意数据,client本地接收数据造成漏洞。

本文主要学习两种针对client端的攻击方式:

  1. 恶意server端返回值:Server 端返回给 Client 端恶意的返回值,Client 端反序列化触发漏洞。

  2. 动态类加载:Server 端返回给 Client 端的值为不存在的类对象或子类对象,要求 Client 端去 codebase 地址远程加载恶意类触发漏洞。

1.恶意server端返回值

比如调用的方法返回值为Object类型,server端可以返回恶意的类对象,Client 端接收并反序列化从而触发漏洞。

2. 动态类加载

攻击场景

攻击的目标为一个rmi client端,可以连接我们启动的rmi server端,并且目标可以动态类加载。

利用条件

  1. 安全管理器:由于Java SecurityManager的限制,默认是不允许远程加载的,如果client接收数据后,需要进行远程加载类,需要安装RMISecurityManager并且配置java.security.policy

  2. 自动加载远程类文件属性:属性 java.rmi.server.useCodebaseOnly 的值必须为false。但是从JDK 6u45、7u21开始,java.rmi.server.useCodebaseOnly 的默认值就是true。当该值为true时,将禁用自动加载远程类文件,仅从CLASSPATH和当前虚拟机的java.rmi.server.codebase 指定路径加载类文件。使用这个属性来防止虚拟机从其他Codebase地址上动态加载类,增加了RMI ClassLoader的安全性。

测试环境及分析

使用:https://github.com/longofo/rmi-jndi-ldap-jrmp-jmx-jms

启动remote-class/src/main/java/com/longofo/remoteclass/HttpServer在本地启动一个http服务托管类文件。

启动java-rmi-server/src/main/java/com/longofo/javarmi/RMIServer1.java启动一个存在漏洞的rmi server,在实战中,该rmi server由攻击者自己搭建。

启动java-rmi-client/src/main/java/com/longofo/javarmi/RMIClient1.java,模拟client端连接我们构造的server端,造成client端命令执行。

server端绑定的是ServicesImpl1对象,ServicesImpl1.sendMessage()方法返回ExportObject类对象,而在client端实现的接口中,该方法返回Object类型,ExportObject在client端是不存在的,所以client就会去 codebase 地址远程加载恶意类触发漏洞。

注意事项

我们攻击的是client端,在client端需要安装RMISecurityManager并且配置java.security.policy,源码中使用如下方式获取策略文件:

System.setProperty("java.security.policy", RMIServer.class.getClassLoader().getResource("java.policy").getFile());

我的项目路径中存在中文,导致无法正常获取策略文件。

解决办法,直接在d盘根目录创建策略文件java.policy

System.setProperty("java.security.policy", "D:/java.policy");

利用该漏洞需要server端允许加载远程类,即java.rmi.server.useCodebaseOnly的值为false。

直接打的jdk版本:< JDK 6u45、JDK 7u21

高版本jdk复现:设置属性 System.setProperty("java.rmi.server.useCodebaseOnly", "false");

工具

https://github.com/waderwu/attackRmi

https://github.com/A-D-Team/attackRmi

学习各种payload的构造方式。

参考文章

https://paper.seebug.org/1091/#rmi

https://su18.org/post/rmi-attack/

http://www.codersec.net/2018/09/%E4%B8%80%E6%AC%A1%E6%94%BB%E5%87%BB%E5%86%85%E7%BD%91rmi%E6%9C%8D%E5%8A%A1%E7%9A%84%E6%B7%B1%E6%80%9D/


文章来源: http://mp.weixin.qq.com/s?__biz=Mzg3OTEwMzIzNA==&mid=2247484539&idx=1&sn=7a225cb14b72936cc79eb9c38fa6f309&chksm=cf08d816f87f5100276045d50d0805e6e5fa1861a58bdbbe729f4abd6d58ec5e0b43d320be2c#rd
如有侵权请联系:admin#unsafe.sh