360-CERT [三六零CERT]???? 前天
0x00 漏洞简述
随着RMI
的进一步发展,RMI
上的反序列化攻击手段正逐渐增多,该类漏洞最近正受到愈加广泛的关注。
RMI (Java Remote Method Invocation)
是Java
远程方法调用,是一种允许一个 JVM
上的 object
调用另一个 JVM
上 object
方法的机制,在Java RMI
的通信过程中存在反序列化漏洞,攻击者能够利用该漏洞造成代码执行,漏洞等级:高危
。
在JDK8u231
之前的JDK
版本,能够让注册中心反序列化UnicastRef
这个类,该类可以发起一个JRMP
连接到恶意服务端上,从而在DGC
层造成一个反序列化,因为DGC
层的filter
是在反序列化之后进行设置的,没有起到实际作用,在JDK8u231
进行了修复,在DGC
层反序列化之前就为InputStream
设置了filter
,来过滤传入的序列化数据,提高安全性。
国外安全研究人员@An Trinhs
发现了一个gadgets
利用链,能够直接反序列化UnicastRemoteObject
造成反序列化漏洞。
该漏洞的相关技术细节已公开。
对此,360CERT建议广大用户及时将JDK
升级到最新版本,下载地址为:Java SE Downloads 。与此同时,请做好资产自查以及预防工作,以免遭受黑客攻击。
https://www.oracle.com/java/technologies/javase-downloads.html
0x01 影响版本
- JDK:<=8u231
0x02 漏洞详情
JEP290
机制是用来过滤传入的序列化数据,以提高安全性,在反序列化的过程中,新增了一个filterCheck
方法,所以,任何反序列化操作都会经过这个filterCheck
方法,利用checkInput
方法来对序列化数据进行检测,如果有任何不合格的检测,Filter
将返回Status.REJECTED
。但是jep290
的filter
需要手动设置,通过setObjectInputFilter
来设置filter
,如果没有设置,还是不会有白名单的。 jdk9
向下增加jep290
机制的jdk
版本为。
UnicastRemoteObject.readObject
当我们反序列化UnicastRemoteObject
这个类时,由于该类重写了readObject
方法,所以在反序列化的时候会调用到他的reexport
方法。
在reexport
方法里,如果ssf
是被我们设置了值,那么进入else
判断
接着调用exportObject
方法,该方法通常用来导出远程对象。
一直跟进,跟到TCPTransport.exportObject
方法。
继续跟进listen
方法,跟进TCPEndpoint.newServerSocket
方法。
这里如果我们把ssf
设置为通过RemoteObjectInvocationHandler
生成的代理类,那么就会调用到RemoteObjectInvocationHandler.invoke
方法。(这里涉及到动态代理的知识,如果调用通过InvocationHandler
的实现类生成的代理类,那么会转而调用实现类的invoke
方法,并且会向invoke
方法传入三个参数:代理类对象作为proxy
参数传入,调用的代理类方法作为method
参数传入,具体方法的参数作为args
参数传入),在invoke
方法中,检测声明方法的类,如果不为Object
,进入invokeRemoteMethod
方法。
检测Proxy
的需要实现Remote
接口,这里是我们能控制的,因为在创建代理类的时候就需要指定实现的接口,这里的ref
被赋值为UnicastRef
,并且存有恶意服务端(这里我们的注册中心一端转变成客户端,而恶意监听的一端相当于服务端)的tcp
信息,这里是我们在序列化数据的时候设置的。
UnicastRef.invoke
然后,有几处代码比较关键。
第一处:是和服务端建立连接。第二处:向服务端传递一些byte
信息。第三处:获取OutputStream
,进行远程方法传参,这里涉及到调用marshalValue
序列化参数传递给服务端,不过这一处与本次绕过关系不大,所以用的细线标注。第四处:服务端反序列化参数之后,会向客户端传值,如果服务端反序列化成功,会发送byte
值1
给客户端,如果发生一些错误,就会发送byte
值2
给客户端。
这里的byte
值1
和2
主要体现在客户端的executeCall
方法里,
可以发现,如果客户端接受到服务端返回的byte
值是2
,那么就有一个readObject
方法,而且我们看到getInputStream
之后,并没有给该Stream
设置jep290
的filter
,那么这里就可以造成注册中心(客户端)的反序列化。
调用栈
漏洞利用
- 需要一个向服务端发起
JRMP
连接的UnicastRef
,这里ysoserial
有相关代码,并且在LocateRegistry.getRegistry
里也有相应代码。
- 创建
RemoteObjectInvocationHandler
的代理类,ssf
是RMIServerSocketFactory
类型。
- 将
ssf
的值设置成创建的代理类,但是由于是private
类型,所以需要用反射来赋值。
然后我们利用Server
端进行bind
,让注册中心反序列化这个UnicastRemoteObject
对象,不过序列化的时候出现了问题,在调用RegistryImpl_Stub.bind
的时候,进行writeObject
的时候。
如果enableReplace
为true
。
检测我们要序列化的obj
,是否实现Remote/RemoteStub
,由于UnicastRemoteObject
实现了Remote
,没有实现RemoteStub
,于是会进入判断,就会替换我们的obj
,以至于反序列化的时候不能还原我们构造的类。
所以,需要把enableReplace
改为false
。这里可以自己实现重写RegistryImpl_Stub
,将bind
方法进行修改,在序列化之前,通过反射,把enableReplace
值进行修改。
- 恶意服务端只需要利用
ysoserial.exploit.JRMPListener
开启,监听到来自客户端的请求之后,就会向客户端发送byte
值2
,并序列化恶意类,最终让客户端反序列化恶意类。
jdk8u241中的修复
在jdk8u241进行了修复,在调用UnicastRef.invoke
之前,做了一个检测。
声明方法的类,必须要实现Remote
接口,然而这里的RMIServerSocketFactory
并没有实现,于是无法进入到invoke方法,直接抛出错误。
0x03 时间线
2020-07-24 360-CERT 发布分析报告
0x04 参考链接
AN TRINHS RMI REGISTRY BYPASS
[https://mogwailabs.de/blog/2020/02/an-trinhs-rmi-registry-bypass/]