论RMI的攻防演进史
前些日子的一次比赛碰到攻击RMI服务的漏洞,最终没打下来。当时也被其他任务缠身导致没探究其根本原因。想着近两年各种基于RMI的漏洞又多了起来,而我对其中涉及的很多JDK版本问题、官方修复绕过、分布式垃圾回收相关特性、各种tricks的利用等都懵懵懂懂。趁着假期,索性一次将RMI相关的利用问题搞清楚
本文不涉及太多Debug代码的流水账,那样只会绕来绕去把自己绕晕。而是按照Oracle的官方修复、被绕过、修复、再绕过的思路进行分析。全文讲的RMI攻击对象为注册中心及服务端,至于反向操作造成的客户端反打问题原理类似,这里不做讨论。
针对RMI服务的利用手法依赖于目标ClassPath存在的Gadget,从JDK更新历史来看可分为三个阶段,第一阶段是在JEP290之前,攻击者可使用bind/unbind/dirty等操作绑定Gadget完成利用。在第二阶段是在发布JEP290(JDK8u121)至JDK8u241时期,由于JEP290 白名单的限制,进而找出了UnicastRef、UnicastRemoteObject利用链可用于二次反序列化的攻击手法,中间也穿插了对来源地址等的限制及绕过(CVE-2019-2684)。而第三阶段是在JDK8u241之后,攻击RMI服务已经无法利用bind/unbind/dirty等内置方法完成攻击,只能寄希望于寻找应用层的函数方法,当方法传递的是Object、Remote、Map等类型参数时,还是可以利用其传递构造的恶意对象进行利用。
1、概念介绍
先了解下RMI相关名词RPC、RMI、JNDI、JRMP等的解释
RPC
RPC 全称为 Remote Procedure Call(远程过程调用),它是一种计算机通信协议,允许像调用本地服务一样去调用远程服务。RPC可以有不同的实现方式,如RMI(远程方法调用)、Hessian等。另外RPC与语言是没有关系的,Java rmi使用的是Socket通信、动态代理反射、Java原生反序列化去实现的RPC框架。即本文要讲解的重点-Java rmi
Java RMI
Java RMI 全程为Java Remote Method Invocation(Java 远程方法调用)。Java RMI是专门为Java提供的远程方法调用机制,远程服务器实现具体方法并提供接口,本地客户端根据接口定义,提供参数即可调用远程方法并获取执行结果,实现了跨域JVM去调用另外一个JVM的对象方法。
JNDI
JNDI全称为Java Naming and Directory Interface(Java 命名与目录接口)。命名指的是在一个目录系统当中,实现了”服务名称-对象/引用”这样的映射对应关系,当客户端根据名称即可查询到相关联的对象/引用。目录是一种特殊类型的命名服务,在命名的基础上增加了“属性”的概念,所以客户端也可以根据对象属性操作筛选对象/引用。这些对象/引用可以存储在不同的命名/目录服务当中,如上面提到的远程服务调用RMI、公共对象请求代理架构CORBA、轻量级目录访问协议LDAP、域名服务DNS
JRMP
JRMP全称为Java Remote Method Protocal(Java 远程方法协议)。Java本身对于RMI的实现默认使用JRMP协议,而最近几年的漏洞之王-Weblogic对于RMI的实现使用的是T3协议。一次Java RMI的过程,需要用到JRMP协议去组织数据格式然后通过TCP协议进行传输,达到远程方法调用的目的
2、RMI调用基本流程
一次完整的RMI调用涉及到注册中心Registry、服务端Server、客户端Client三端,服务端首先向注册中心注册创建的远程对象(下图第一二步),接着客户端向注册中心发起查找请求并拿到远程对象的存根(下图第三四步)。RMI的实现引入了Stubs(客户端存根)、Skeletons(服务端骨架)两个概念。当客户端调用远程服务端的对象方法时(下图第五步),实际上会先经过“远程对象的客户本地代理”,这个代理类就是Stubs(客户端存根),其主要负责将要调用的远程方法名及参数打包、并将该包转发给远程对象所在的服务器(下图第六步);而在远程服务器调用真正的方法之前,同样也会经过代理类,这个存在于服务端的代理类就是骨架Skeleton,它从Stubs中接受调用并传递给真实的目标方法(下图第七步)。两者对于RMI服务的使用者是隐藏的,使用者不需要关注这部分实现。如下图是一次RMI的调用过程
了解了RMI的调用过程,我们还需要知道:RMI过程中传输的数据均为序列化数据,服务端/客户端/注册中心在拿到数据后都会进行反序列化操作。如果传输的是我们构造好的恶意序列化数据,就会在反序列化时触发漏洞。关于RMI的大部分攻击都是基于此特性完成的,主要分为:服务端向注册中心的bind操作、客户端向注册中心的lookup操作、客户端向服务端调用“自定义方法”的操作。漏洞利用在此基础上完成。本文主要讨论的是对于注册中心/服务端的漏洞利用在JDK中的的攻防历史,涉及多个JDK版本、反序列化Gadget构造技巧、官方的修复与绕过等等知识。
文章中使用的JDK版本:
1 |
|
2、JAVA RMI与JDK的攻防史
1、jdk< 8u121 无任何过滤
1.1 bind/rebind 的利用
服务端使用bind向注册中心注册绑定远程对象,我们可以放置恶意对象完成利用
sun.rmi.registry.RegistryImpl_Skel#dispatch
注册中心端的RegistryImpl_Skel会直接对服务端 bind/rebind操作传输过来的对象进行反序列化而没有任何过滤。所以在JDK8u121之前可以直接使用bind/rebind操作传输恶意对象进行漏洞利用。这也是ysoserial.exploit.RMIRegistryExploit 利用的原理。因为bind传输的类必须实现Remote接口,可使用动态代理的方式进行解决,ysoserial使用的handler为AnnotationInvocationHandler
1.2 DGC#dirty 的利用
因为在跨虚拟机的情况下,RMI无法直接使用原有JDK的GC机制,而自己实现了DGC(Distributed Garbage Collection 分布式垃圾回收),在对RMI进行漏洞利用的时候,也会出现经常出现DGC的身影。与上面RMI流程图中提到的一样,DGC也具有Stubs(客户端存根)、Skeletons(服务端骨架)两个概念,涉及的类为:DGCImpl_Stub、DGCImpl_Skel。并且只要启动了RMI服务,那么一定会存在DGC,其传输的数据是序列化数据,参数ObjID为Object类型,可以放置我们的恶意payload。在ysoserial中,DGC对应的利用exp为ysoserial/exploit/JRMPClient,以下为细节分析
处理DGC操作的是sun.rmi.transport.DGCImpl_Skel#dispatch方法
根据传入的var3值决定是调用clean(0)操作还是dirty(1)操作,在调用真正的远程方法之前会使用readObject()获取参数值,即进行反序列化操作,这也是该漏洞的触发点。而EXP编写的重点是如何把我们构造的恶意序列化数据塞进去。这涉及到DGC通信的一些协议格式,我们要解决的问题本质上来说就是:模仿客户端通信,将构造的恶意数据塞入数据流,使得服务端通过反序列化操作获取ObjID参数值时触发漏洞。并且由于DGC对于RMI使用用户来说并不可见,无法像registry可以直接连接去调用内置方法,而是需要自己起socket请求,按照数据格式进行数据填充。
参考rmi-protocol-docs发送的报文格式如下。服务端在接收到客户端传输的数据后,依次解析确认operation指令(Call、Ping、DgcAck)、根据ObjID确认处理的Skel类(RegistryImpl_Skel/DGCImpl_Skel/自定义)、根据num/hash确认要调用的方法、arg为调用方法的参数值。其中ObjID、num、hash、arg都是基于JAVA原生序列化机制生成的序列化数据
Header默认值部分在TransportConstants中定义,其中文档中的0x4a 0x52 0x4d 0x49
即sun.rmi.transport.TransportConstants#Magic的值
operation:
1 |
|
ObjID:Registry与DGC的ObjID是固定值,在如下函数中被定义
1 |
|
num:Registry与DGC中的操作及对应值
1 |
|
hash:Registry与DGC中hash值为固定值,自定义方法的hash值为方法签名的sha1
1 |
|
sun.rmi.server.UnicastServerRef#dispatch 根据客户端传过来的num值进行判断,如果≥0,表示为Registry/DGC默认方法 调用sun.rmi.server.UnicastServerRef#oldDispatch进行处理,如果客户端想远程调用自定义方法,则需要在定义时将属性值num设为负值、服务端在接收到客户端发送的call指令后根据num及hashToMethod_Map.get(方法hash值)
确认目标方法,最后通过反射进行调用
而arg为远程方法的参数值,是基于JAVA原生序列化机制生成的序列化数据。在DGC层clean/dirty方法的ObjID参数为Object类型,可以承载我们的恶意payload,其对应的EXP为ysoserial.exploit.JRMPClient,数据构造部分在makeDGCCall()中
至此即可通过DGC攻击RMI服务
2、jdk = 8u121 (JEP290)
在jdk=8u121的时候,ORACLE官方做了两件事情。分别影响的是”远程加载类攻击客户端手法“、”对注册中心及DGC的反序列化攻击手法(加上了全局白名单)“。JEP290对于Java原生反序列化的影响暂不讨论,本文主要分析JEP290对RMI Registry、RMI DGC等攻击利用方式的影响。
2.1 限制1:RMI Registry、RMI DGC 增加了反序列化白名单
RMI Registry(注册表)、RMI DGC(分布式垃圾收集器)都默认启用了反序列化filter机制,只允许反序列化白名单中的特定类。这两者与我们对于RMI服务的攻击利用息息相关。
a.RMI Registry内置了白名单过滤器,只允许在注册表中绑定(bind)白名单中的类的实例
其验证逻辑在sun/rmi/registry/RegistryImpl.java#registryFilter。另外可以自行编辑sun.rmi.registry.registryFilter
系统属性配置黑白名单为RMI注册表增加额外保护
b.RMI DGC与RMI Registry类似,也内置了反序列化的白名单,包括:java.rmi.server.ObjID
、java.rmi.server.UID
、java.rmi.dgc.VMID
和java.rmi.dgc.Lease
。这部分逻辑写在sun.rmi.transport.DGCImpl#checkInput
2.2 限制2:限制了 RMI 远程加载机制
JDK RMI的远程Reference信任机制变化:环境变量com.sun.jndi.rmi.object.trustURLCodebase默认为false,意味着我们不能通过rmi的JNDI方式去攻击客户端了
有关JNDI注入修复及绕过分析可参考之前文章:当我们谈论JNDI注入时,我们在谈论什么
2.3 绕过1:使用JEP290白名单中的UnicastRef完成绕过
总结:JEP290是对RMI Registry与RMI DGC做的白名单限制,并没有对JRMP回连逻辑做限制,而白名单中的UnicastRef类会建立JRMP请求并对返回数据做反序列化处理,所以导致二次反序列化问题
JEP290 加上了反序列化白名单:sun/rmi/registry/RegistryImpl.java#registryFilter
1 |
|
前辈在白名单中找到UnicastRef类,此类的readExternal()方法会构建LiveRef对象(用于建立JRMP连接),sun.rmi.registry.RegistryImpl_Skel调用dispatchsun.rmi.transport.StreamRemoteCall#releaseInputStream释放输入流的时候会建立JRMP连接,并从数据流中取出数据进行反序列化操作,所以我们可利用JEP290白名单中的UnicastRef类进行一个二次反序列化绕过限制。利用思路如下:
2.3.1 UnicastRef 链利用复现
1、攻击者搭建恶意JRMP服务器,并放置构造的恶意序列数据等待目标服务器来取。这部分逻辑对应ysoserial.exploit.JRMPListener类,使用命令为
1 |
|
2、RMI Registry反序列化UnicastRef类,从UnicastRef#readExternal()一直调用到StreamRemoteCall#executeCall,与恶意JRMP服务器建立链接,并取回序列化数据进行反序列化操作,这时候的RMI Registry相当于客户端
指定jrmp 连接基础代码:
1 |
|
3、RMI Registry反序列化我们构造好的恶意序列化数据,完成漏洞利用
2.3.2 UnicastRef 包装恶意Padyload
UnicastRef gadget chain:
1 |
|
具体调用链如下,readExternal()会向ConnectionInputStream对象中存储Ref信息(包含jrmp链接的host、port等信息),然后再调用sun.rmi.transport.StreamRemoteCall#releaseInputStream一直到sun.rmi.server.UnicastRef#invoke中对jrmp服务端返回的数据进行反序列化操作。这两处操作需要注意下,后面的JDK修复也是针对这两处进行修复的
1 |
|
sun.rmi.transport.StreamRemoteCall#executeCall 对JRMP返回的数据进行反序列化操作
相对应的EXP构造在ysoserial.exploit.JRMPListener#doCall中,先往数据流写入ExceptionalReturn值(2),接着写入我们的恶意Payload
2.3.3 Remote接口类包装UnicastRef类
利用思路是没问题了,但是还有一个问题:我们如何将UnicastRef发送到RMI Registry,这个类并没有实现Remote接口,所以无法直接绑定到注册中心
只有将UnicastRef对象包装为Remote类型才能继续绑定。有几种方法能做到:
1 |
|
其实ysoserial.exploit.RMIRegisterExploit中使用的就是第一种方法,作者使用的handler是sun.reflect.annotation.AnnotationInvocationHandler,将其动态代理为Remote类型。但是这个类并不在JEP290白名单中,无法满足要求。所以需要重新找。
第二种思路,找到了RemoteObjectInvocationHandler,这个类的父类RemoteObject具有一个RemoteRef类型(UnicastRef实现了此接口)的属性,并且本身实现了Remote接口。满足我们的要求
但是我们可以看到ref属性是transient 关键字修饰,表明ref属性默认不被序列化,那我们找的这个类是不是不满足需求了呢?并不是!我们查看RemoteObjectInvocationHandler父类java.rmi.server.RemoteObject重写了writeObject(),其利用了writeExternal来写入被transient 修饰的ref属性值。所以依旧可以被反序列化
我们直接使用RemoteObjectInvocationHandler包裹下UnicastRef对象即可。
而ysoserial.payloads.JRMPClient中利用了RemoteObjectInvocationHandler可动态代理为任意接口的特性,将UnicastRef对象转化为Remote子接口Registry进行传递。其实不用这么复杂,直接使用RemoteObjectInvocationHandler包装一下就OK了
综上看来,RemoteObject其实更符合我们的要求:属性ref可包裹UnicastRef对象,其本身又实现了Remote接口。那他的子类理论上来说均是可以满足要求的。
但是事实并不是我们想象的那样,调试发现在操作序列化写入数据时,会进行判断(enableReplace值默认为true),如果满足”目标类实现Remote接口 && 未实现RemoteStub接口 && “则会将我们构造的恶意类替换。从而导致攻击利用失败。如下是调用过程替换方法replaceObject()的逻辑代码
java.io.ObjectOutputStream#writeObject0
sun.rmi.server.MarshalOutputStream#replaceObject
而RemoteStub是RemoteObject的子类,所以我们要找的目标类只需要缩小范围,只找RemoteStub子类即可满足要求。
1 |
|
经测试,这些类直接包裹unicastRef对象就可以完成利用
另外也可以利用反射修改enableReplace值,则RemoteObject的子类均可用了
3、jdk = 8u141
3.1 限制1:RMI bind/rebind/unbind 操作限制来源地址为本地
其实在jdk的早期版本中bind/unbind/rebind操作都会限制地址,只不过校验是在反序列化之后进行的,所以并没有对我们进行漏洞利用产生影响。但是在8u141的更新中限制了RMI bind/rebind/unbind 操作限制来源地址为本地地址。如下图是jdk8u121的sun/rmi/registry/RegistryImpl_Skel.java#dispatch()代码:反序列化操作完成之后才进行sun.rmi.registry.RegistryImpl#checkAccess地址检测
oracle官方在8u141对此处做了修改,防止外部攻击者的恶意对注册表进行bind/unbind操作。下面是jdk8u121与8u141的对比,可以发现将checkAccess操作提前至反序列化之前。
这就影响了ysoserial.exploit.RMIRegistryExploit的使用,此exp正是通过bind恶意类到注册中心完成攻击的。那有没有其他操作可以帮助我们完成恶意序列数据的传递呢。观察同文件下的其他操作,lookup()用于客户端向注册端查询,直接对数据流进行readObject()操作,并且没有checkAccess()地址来源校验。满足我们的要求
3.2 绕过1:RMI lookup 绕过对来源地址的限制
根据上一小节描述,我们可以晓得在8u141及之后,即使使用白名单中的UnicastRef类绕过了JEP290,官方对bind/unbind/rebind操作的限制来源为本地,导致无法完成利用。我们看到在同文件下的lookup方法满足要求(1、未检查来源地址;2、虽然传递的是String类型参数,但是在写入使用的是writeObject操作),我们无法直接拿sun.rmi.registry.RegistryImpl_Stub#lookup来使用,需要进行简单改造,使其支持传入Object类型参数
sun.rmi.registry.RegistryImpl_Stub#lookup
我们仿照逻辑重写一个支持传入Object类型参数的lookup方法
1 |
|
3.3 绕过2:绕过本地地址限制(CVE-2019-2684)
在1.2节我们演示了构造DGC层数据的构造、在3.2节我们重写了lookup方法使其可以传入Object类型的参数。对于此类RPC的调用,数据全部由客户端构造,攻击者可以任意更改传输数据去应对服务端的过滤处理逻辑。而回到我们这里讨论的JDK8u141加上localhost限制,我们类比DGC层数据构造、改造lookup方法的操作,可以动手改造bind方法去解决。关于该绕过,貌似关注的人极少。且Ysoserial也未对此限制绕过编写EXP,所以这里动手写一下。具体思路由如下两种:
1、重写bind逻辑,使得在判断时进入lookup的处理逻辑进而触发UnicastRef反序列化链。
2、重写bind逻辑,使服务端进入“调用自定义方法“的逻辑,而自定义方法的参数是序列化参数,并未进入oldDispatch,导致可以绕过localhost的判断
思路1其实本质来说与3.2的绕过1是相同的,均利用了未做鉴权的lookup方法
绕过的exp 1:
我们看下思路2:
当opnum<0时表明是用户自定义方法,服务端根据hashToMethod_Map.get(方法hash值)
确认目标方法。但是其内置了Registry的5个操作方法,我们只需要传入对应方法的hash值即可。
最终使用sun.rmi.server.UnicastRef#unmarshalValue组装”自定义方法参数”时调用readObject设置JRMP反向连接、releaseInputStream()释放数据流时请求恶意服务触发二次反序列化
最终通过重写lookup、bind的方式完成了本地地址限制的绕过
4、jdk = 8u231
4.1 限制1:RMI 修复UnicastRef链绕过的问题
1 |
|
4.1.1 清除UnicastRef的反连地址
在JDK8U231版本在sun.rmi.registry.RegistryImpl_Skel#dispatch处理bind、lookup、rebind、unbind操作时增加了sun.rmi.transport.StreamRemoteCall#discardPendingRefs方法,当反序列化时发生IO/类找不到或类型转换错误时,会调用sun.rmi.transport.ConnectionInputStream#discardRefs方法,去掉UnicastRef的反连地址(之前存储地址时使用的是ConnectionInputStream#saveRef),导致UnicastRef JRMP外连链无法利用
当服务端处理“由客户端改造的lookup()传输的UnicastRef恶意数据”时,readObject会正常执行,但是当转为String类型时触发catch ClassCastException错误,进入discardPendingRefs进行清除数据。我们可以看到incomingRefTable在处理前后的对比
执行discardPendingRefs操作后
4.1.2 对反连拿到的对象进行白名单校验
另外Registry在处理JRMP反连操作时会最终会调用到sun.rmi.transport.DGCImpl_Stub#dirty方法,并在this.ref.invoke(var5);操作中触发反序列化操作,8u231在invoke前增加了白名单限制sun.rmi.transport.DGCImpl_Stub#leaseFilter导致
返回的序列化对象无法通过检测
leaseFilter白名单:
1 |
|
4.2 绕过1:使用UnicastRemoteObject链绕过修复
这条链与 之前绕过JEP290的UnicastRef 链不同之处在与它并不是在 StreamRemoteCall#releaseInputStream中触发JRMP外连,而是在调用readObject的时候就触发了,所以可以绕过8u231的修复补丁。这条链是由An Trinh 发现并在19年Blackhat上公布的,详情可参考:https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf
相当于从UnicastRemoteObject.readObject()通过”一系列操作“ 最终调用到了UnicastRef.invoke(),刚好绕过官方的两步修复方案。UnicastRef链及8u231的修复方案
1 |
|
我们观察修复方案可以发现:官方并没有处理sun.rmi.server.UnicastRef#invoke之后的操作,相当于sink点没变,绕过补丁需要找一处反序列化的source点,source点需要满足如下条件:
1 |
|
顺着这个思路,找到JEP290的白名单中有个java.rmi.server.UnicastRemoteObject,这个类的readObject()方法最终会调用到其属性值ssf的createServerSocket方法
这里用到了动态代理的特性:当调用ssf属性的createServerSocket方法时,会调用handler.invoke(),即这里会调用RemoteObjectInvocationHandler#invoke
而RemoteObjectInvocationHandler的ref属性为我们构造的UnicastRef对象,所以会调用到sun.rmi.server.UnicastRef#invoke(java.rmi.Remote, java.lang.reflect.Method, java.lang.Object[], long),接下来就与UnicastRef链一致了
最终的调用链:
1 |
|
编写exp:
1 |
|
使用UnicastRefRemoteObject链绕过官方对于UnicastRef链的修复
5、jdk = 8u241
5.1 修复1:RMI 修复UnicastRefRemoteObject链绕过的问题
在jdk8u241对UnicastRefRemoteObject链的利用做了修复,有两处:
1、sun.rmi.registry.RegistryImpl_Skel的bind、lookup、unbind传输的String类型参数使用readObject(String.class)进行反序列化操作
2、java.rmi.server.RemoteObjectInvocationHandler#invokeRemoteMethod 在调用ref.invoke前检测Method对象表示方法所在类的Class对象(即这里Gadget chain中的RMIServerSocketFactory)是否实现了Remote接口
这两处补丁针对性修复了UnicastRefRemoteObject链,具体如下
sun.rmi.registry.RegistryImpl_Skel#lookup
java.rmi.server.RemoteObjectInvocationHandler#invokeRemoteMethod
var2 是调用栈中触发代理handler的方法(createServerSocket), 我们无法控制此参数,Gadget中的关键类RMIServerSocketFactory没有实现Remote接口导致反序列化中断失败。修复的调用栈如下:
1 |
|
6、jdk ≥ 8u241 的利用方式—应用层反序列化问题
目前如果目标的JDK版本大于或等于8u241,暂无法利用内置方法完成攻击。但是还可以寻找应用程序级别的方法,当传递的是Object、Remote、Map等类型参数时,我们可以利用其传递构造的恶意对象进行利用。
在3.2章节我们利用“调用自定义方法”的逻辑去调用了内置的bind方法,系统在处理参数时使用反序列化操作无过滤导致出现问题。这里利用的也是这个原理,当客户端调用服务端自定义方法时,服务端根据hashToMethod_Map.get(方法hash值)
确认目标方法、unmarshalParameters解析参数、最后invoke反射调用
在sun.rmi.server.UnicastServerRef#unmarshalParametersUnchecked方法中对每个参数依次解析
sun.rmi.server.UnicastRef#unmarshalValue 当参数类型非基本数据类型、非String类型时直接调用readObject
7、错误排查
在漏洞利用过程中会出现各种报错,本节分析各种报错出现原因及对应解决绕过方案
7.1 ObjectInputFilter REJECTED
当使用Ysoserial的ysoserial.exploit.RMIRegistryExploit结合CommonsCollections6利用链攻击目标RMI服务器时出现该报错
目标RMI服务日志信息:
1 |
|
攻击者日志信息:
1 |
|
这种情况下是本文2.1限制1中提到的JEP290生效,用于包装CommonsCollections6的AnnotationInvocationHandler不在JEP290的白名单中导致漏洞利用失败。利用UnicastRef链绕过即可
7.2 Registry.Registry.bind disallowed
当使用绕过JEP290的UnicastRef链结合RMIConnectionImpl_Stub类攻击目标RMI服务器时出现该报错
攻击者日志信息:
1 |
|
目标环境为JDK8u121
目标环境为JDK8u141
仔细查看发现虽然报错都是Registry.Registry.bind disallowed,但是前者利用成功,后者却失败了。141的调用栈并没有执行到RegistryImpl.bind。该种情况与本文3.1章节中分析的一致,Oracle官方将checkAccess地址检查从RegistryImpl.bind提前到了RegistryImpl_Skel.dispatch,导致漏洞利用失败。所以我们根据报错可以推断出利用情况:如果调用栈执行到了RegistryImpl.bind再报错,说明漏洞利用已经完成,反之则说明目标JDK版本大于等于8u141,需要使用我们改造的lookup进行利用
7.3 java.lang.ClassCastException: xxx cannot be cast to java.lang.String
当使用绕过JEP290—local限制的UnicastRef链结合RMIConnectionImpl_Stub类、改造的lookup()攻击目标RMI服务器,效果及报错如上图。虽然报类型转换错误,但是漏洞已经利用完成
7.4 Cannot cast an object to java.lang.String
当使用UnicastRemoteObject链结合改造的lookup()攻击基于JDK8u241的目标RMI服务器时出现该报错
攻击者日志信息:
1 |
|
这种情况是本章5.1提到的Oracle官方在JDK8u241用于修复UnicastRefRemoteObject链的补丁,最终在反序列化时报错java.io.ObjectInputStream#readObject0
这种情况下说明目标的JDK版本高于或等于8u241版本,目前只能使用应用层的方法进行利用了
3、总结
本文基于Oracle官方对于RMI利用的修复历史,依次分析了JDK8u121的JEP290修复绕过、JDK8u141的来源限制、JDK8u231对于UnicastRef链的修复、JDK8u241对于UnicastRefRemoteObject链的修复及各补丁的绕过情况。这部分知识网上资料很多,但大多是分析单个版本的利用手法、修复及绕过。自己看了一圈后,感觉还是懵懂,深知自己对于这部分内容的储备及理解不够,遂花了亿点时间整理此万字长文。也希望对各位学习这部分知识的师傅有帮助。
4、参考
https://i.blackhat.com/eu-19/Wednesday/eu-19-An-Far-Sides-Of-Java-Remote-Protocols.pdf
https://su18.org/post/rmi-attack/
https://mogwailabs.de/en/blog/2019/03/attacking-java-rmi-services-after-jep-290/
https://www.anquanke.com/post/id/197829
http://code2sec.com/cve-2017-3241-java-rmi-registrybindfan-xu-lie-hua-lou-dong.html
https://xz.aliyun.com/t/7932