攻击JavaRMI概述

2024-03-12 13:40
文章标签 java 概述 攻击 rmi

本文主要是介绍攻击JavaRMI概述,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

编者注:

1.本文作者为默安科技逐日实验室,如需转载或讨论,请联系【默安逐日实验室】公众号。

2.本文仅供学习研究,严禁从事非法活动,任何后果由使用者本人负责。

可能师傅们也遇到过这种情况:因为问题A跟进某个类方法,调试期间遇到一个更纠结的问题B,在费尽周折终于搞清楚后,蓦然回首却忘了自己在干什么(问题A)......

JavaRMI与相关攻击面错综复杂,因此本文仅以时间线梳理各种绕过方法与缓解措施的主要逻辑,而不会详述具体调用栈(但会指出关键类方法)。大家可以自行跟进加深印象,也可以参考其它师傅的文章云调试。

RMI全称是Remote Method Invocation(远程方法调用),可以理解为远程过程调用(RPC)的Java实现版本。它使得应用程序员可以像调用本地方法一样(stub.func(arg)),调用远程主机上提供的方法,方法实际最终在远程主机上执行(serv.func(arg)),原因是本地类封装了与远程类基于序列化的网络通信(socket)。

我们将上述提供业务方法的远程主机称为服务端,将发起方法调用的本地主机称为客户端。服务端会监听在一个随机端口上,将自己的通信地址等信息封装后(stub)由某种途径交给客户端。这种途径就是默认监听在1099端口上的注册端。

RMI全称是Remote Method Invocation(远程方法调用),可以理解为远程过程调用(RPC)的Java实现版本。它使得应用程序员可以像调用本地方法一样(stub.func(arg)),调用远程主机上提供的方法,方法实际最终在远程主机上执行(serv.func(arg)),原因是本地类封装了与远程类基于序列化的网络通信(socket)。

我们将上述提供业务方法的远程主机称为服务端,将发起方法调用的本地主机称为客户端。服务端会监听在一个随机端口上,将自己的通信地址等信息封装后(stub)由某种途径交给客户端。这种途径就是默认监听在1099端口上的注册端。

攻击业务危险方法

如果远程方法实现中存在输入流可控的危险方法:

clazz {func(arg) {exec(arg)}
}

理论上就跟利用Web应用漏洞一样,RMI此时就是一个毫无存在感的中间层。实际情况是基本没有能直接调用到的危险方法,而且也不知道危险方法的调用接口(clazz.func(arg))。

  • java.rmi.server.useCodebaseOnly在7u21|6u45时由默认false改为true,下文均需要被攻击端存在gadgets。另外为了行文流畅,8u241对String类型反序列化作的变动放在文末统一说明。

攻击远程方法参数

当服务端并不存在危险业务方法,但我们可以拿到远程方法接口类(比如开源应用)。发现它接收一个Object类型参数(func(Object arg, ...)),在此处传入恶意payload对象,服务端从网络接收数据并试图反序列化还原对象时,就会触发gadgets。

相比Object类型,远程方法入参更有可能是int、boolean等基本数据类型,或是String以及其它封装类。客户端会根据方法名及参数类型生成哈希,服务端收到这个哈希就能知道调用的是哪一个方法(sun.rmi.server.UnicastServerRef#hashToMethod_Map)。

那么把恶意对象强行塞给非Object参数能否触发反序列化执行呢?当远程方法参数为非基本数据类型时,sun.rmi.server.UnicastRef#unmarshalValue就会进入readObject所在分支。通过修改网络数据、修改字节码、修改内存对象、修改RMI客户端实现 任意一种方法,将方法哈希修改为服务端存在且符合类型要求的远程方法哈希,即可触发强行塞入的恶意对象。

攻击RMI注册端

抛开具体业务远程方法,Registry也提供了基本方法:

  • bind(String, Remote)
  • list()
  • lookup(String)
  • rebind(String, Remote)
  • unbind(String)

除了list方法不会向注册端传递参数,其余四个方法的任意参数都会被注册端接收并readObject(sun.rmi.registry.RegistryImpl_Skel#dispatch),要解决的问题是如何让恶意对象符合类型要求。

BaRMIe采用代理注册端,通过handleData方法替换字节流数据,最终形成bind(payload, null)。

ysoserial.exploit.RMIRegistryExploit采用动态代理,通过createMemoitizedProxy方法封装为Remote,最终形成bind("pwnedXXX", payload)。

我们也可以自己创建一个类并实现Remote, Serializable,将payload对象赋值放入任意类属性。虽然注册端递归反序列化后最终会找不到自定义类报错,但在此之前会先触发gadgets。

在JEP290(8u121,7u131,6u141)加入的sun.rmi.registry.RegistryImpl#registryFilter会限制递归深度、数组长度,并基于白名单递归检查类型,会拦截payload的最终执行。

此外在8u141将dispatch中原来的先readObject再RegistryImpl.checkAccess(默认仅允许注册端与服务端同地址),修正为了先作检查。会使攻击者伪装成服务端向注册端发起bind/rebind/unbind的攻击失效,但不会影响攻击者作为客户端发起lookup。

攻击DGC服务

DGC全称是Distributed Garbage Collection,顾名思义其实就是GC(垃圾回收)的分布式方案。与Registry类似,DGC服务也提供了基本方法dirty()和clean()。客户端需要用到注册端服务端的远程对象时,会通过dirty申请。相应的,当客户端不再需要时则会通过clean注销。

而同样与Registry类似,注册端服务端上的sun.rmi.transport.DGCImpl_Skel#dispatch,会接收DGC客户端经由dirty或clean传过来的部分数据并readObject,要解决的问题是sun.rmi.transport.DGCImpl_Stub#dirty并没有一个参数接口用于传递对象。

对此,ysoserial.exploit.JRMPClient按照DGC通信的固定格式直接走socket通信在相应位置写入了payload,最终形成[0x4a524d49, 2, 0x4c, 0x50| 2, 0, 0, 0, 1, -669196253586618813L, payload]的数据流。

在JEP290加入的sun.rmi.transport.DGCImpl#checkInput逻辑也与之前相同,会拦截payload的最终执行。

攻击JRMP客户端

RMI和DGC服务都是基于JRMP协议通信,就像HTTP应用与TCP之间会有Web服务器处理HTTP协议一样,JRMP也有相应的处理模块。我们将主动发起JRMP请求的一方称为JRMP客户端,将监听JRMP请求的一方成为JRMP服务端。

JEP290主要通过白名单限制了RMI服务端与DGC服务(注册端或服务端)readObject时能使用的类,却没有限制JRMP客户端处理JRMP服务端返回的异常信息readObject(sun.rmi.transport.StreamRemoteCall#executeCall)。

同上文所述,JRMP服务端的sun.rmi.transport.Transport#serviceCall等位置都是直接writeObject(Exception),所以需要实现恶意JRMP服务端在JRMP客户端发起连接时,将payload走异常接口给抛回去。

ysoserial.exploit.JRMPListener就是这种实现,构造为[81, 2, payload]的数据返回给JRMP客户端,使其进入异常处理触发payload。

寻找Gadgets

  • DGCClient implements the client side of the RMI distributed garbage collection system. The external interface to DGCClient is the registerRefs() method. When a LiveRef to a remote object enters the JVM, it must be registered with the DGCClient to participate in distributed garbage collection. When the first LiveRef to a particular remote object is registered, a dirty() call is made to the server-side DGC for the remote object.

可以知道当LiveRef加载进JVM后,会通过registerRefs注册并发起dirty请求。ysoserial.payloads.JRMPClient以sun.rmi.server.UnicastRef#readExternal充当反序列化入口,在其中装填了sun.rmi.transport.LiveRef,借由DGC机制便可主动发起JRMP请求。

如果有地方能够触发这个反序列化入口,就可以让它成为JRMP客户端向恶意JRMP服务端发起请求,进而走没被限制的异常readObject触发最终payload。刚好UnicastRef是RegistryImpl#registryFilter的白名单类,原汤化原食了。

触发反序列化

ysoserial.exploit.RMIRegistryExploit利用的registry.bind(name, remote)在8u141后失效了,ysomap.exploits.rmi.RMIRegistryExploit使用的Naming.lookup(registry, remote)则依然有效。

同样要解决让第一步的payload符合类型要求的问题。要么依托原RMI客户端发起lookup,找一个实现了Remote且能存放LiveRef的类(比如RemoteObjectInvocationHandler),或者利用递归反序列化特性自己构造类;要么重新实现RMI客户端强行发送数据。

  • 前者找类并测试会遇到一个与类型相关很有意思的问题

8u231捕获了在加载Ref(第一步的payload)后会触发的异常,在发起JRMP请求(releaseInputStream)之前清除了Ref。

并且将dirty和clean中的setObjectInputFilter(DGCImpl_Stub::leaseFilter)过滤器提到了JRMP请求发起之后、恶意JRMP服务端最终gadgets触发之前(ref.invoke(call))的位置。

细看可以发现缓解措施针对的都是加载Ref(第一步的payload)之后的过程,当有一个gadgets能在加载自身就触发JRMP请求,就能绕过这些过滤,An Trinhs找到了一条这样的链:

1. UnicastRemoteObject#readObject

2.RMIServerSocketFactory#createServerSocket

3.RemoteObjectInvocationHandler#invoke

利用动态代理机制封装了类,同时gank了本来的方法调用,将其引入invoke。

从OracleJDK-8u241/OpenJDK-8u242开始将多处readObject再转型String的地方修改为了SharedSecrets.getJavaObjectInputStreamReadString().readString,修复了lookup时的反序列化入口,并且影响上文攻击远程方法参数攻击RMI注册端中涉及String类型参数的地方。

反制攻击方

最后稍微聊一下喜闻乐见的反制问题,ysoserial在477ecb8更新了一个无限制的registry.list(),哪怕在8u261中依然是写着赤果果的readObject:

而在那个commit之前,也许作者是想借由原生的registry.bind(name, remote)得到注册端回显,方便攻击时判断gadgets环境,为此其戴了ExecCheckingSecurityManage作为安全措施(/doge),但还是可以通过CC链读写文件等方式间接溯源或者RCE。

参考内容

Attacking Java RMI services after JEP 290

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

JEP 290: Filter Incoming Serialization Data

Understanding distributed garbage collection

30行代码透彻解析RPC

这篇关于攻击JavaRMI概述的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/801442

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

水位雨量在线监测系统概述及应用介绍

在当今社会,随着科技的飞速发展,各种智能监测系统已成为保障公共安全、促进资源管理和环境保护的重要工具。其中,水位雨量在线监测系统作为自然灾害预警、水资源管理及水利工程运行的关键技术,其重要性不言而喻。 一、水位雨量在线监测系统的基本原理 水位雨量在线监测系统主要由数据采集单元、数据传输网络、数据处理中心及用户终端四大部分构成,形成了一个完整的闭环系统。 数据采集单元:这是系统的“眼睛”,

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟 开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚 第一站:海量资源,应有尽有 走进“智听