WebLogic CVE-2020-2551漏洞分析
2020-03-04 12:06:00 Author: paper.seebug.org(查看原文) 阅读量:531 收藏

作者: 天融信阿尔法实验室
原文链接:https://mp.weixin.qq.com/s/dOycwt_-QpmbuUC8CmxLQQ

一、前言

2020年1月15日,Oracle发布了一系列的安全补丁,其中Oracle WebLogic Server产品有高危漏洞,漏洞编号CVE-2020-2551,CVSS评分9.8分,漏洞利用难度低,可基于IIOP协议执行远程代码。

经过分析这次漏洞主要原因是错误的过滤JtaTransactionManager类,JtaTransactionManager父类AbstractPlatformTransactionManager在之前的补丁里面就加入到黑名单列表了,T3协议使用的是resolveClass方法去过滤的,resolveClass方法是会读取父类的,所以T3协议这样过滤是没问题的。但是IIOP协议这块,虽然也是使用的这个黑名单列表,但不是使用resolveClass方法去判断的,这样默认只会判断本类的类名,而JtaTransactionManager类是不在黑名单列表里面的,它的父类才在黑名单列表里面,这样就可以反序列化JtaTransactionManager类了,而JtaTransactionManager类是存在jndi注入的。

二、漏洞复现

        Hashtable<String, String> env = new Hashtable<String, String>();
        env.put("java.naming.factory.initial", "weblogic.jndi.WLInitialContextFactory");
        env.put("java.naming.provider.url", "iiop://x.x.x.x:7001");
        Context context = new InitialContext(env);
        JtaTransactionManager...

在本地虚拟机上面就成功执行poc了

三、漏洞分析

根据官网漏洞描述及上面poc中可以知道 主要是利用IIOP协议进行的攻击。在分析之前先了解下这些协议的概念

根据oracle官网的文档

RMI 使用RMI,您可以使用Java编程语言编写分布式程序。RMI易于使用,您不需要学习单独的接口定义语言(IDL),并且可以获得Java固有的“编写一次,随处运行”的好处。客户端,远程接口和服务器完全用Java编写。RMI使用Java远程方法协议(JRMP)进行远程Java对象通信。 RMI缺少与其他语言的互操作性,因为它不使用CORBA-IIOP作为通信协议。

IIOP,CORBA和Java IDL IIOP是CORBA的通信协议,使用TCP / IP作为传输方式。它指定了客户端和服务器通信的标准。CORBA是由对象管理组(OMG)开发的标准分布式对象体系结构。远程对象的接口以平台无关的接口定义语言(IDL)描述。实现了从IDL到特定编程语言的映射,将语言绑定到CORBA / IIOP。 Java SE CORBA / IIOP实现称为Java IDL。与IDlj 编译器一起,Java IDL可用于从Java编程语言定义,实现和访问CORBA对象。

RMI-IIOP 以前,Java程序员不得不在RMI和CORBA / IIOP(Java IDL)之间进行选择,以用于分布式编程解决方案。现在,通过遵守一些限制,RMI服务器对象可以使用IIOP协议并与以任何语言编写的CORBA客户端对象进行通信。此解决方案称为RMI-IIOP。RMI-IIOP将RMI风格的易用性与CORBA跨语言互操作性相结合。

oracle官网的RMI-IIOP的例子 ,为了方便IDEA编辑器DEBUG调试我把官网例子稍改了一下,并把客户端和服务端分开存储。

先创建这三个公用的类

//Message.java
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class Message implements Serializable {
    private void readObject(ObjectInputStream s) {
        System.out.println("readObject...");
    }

    private void writeObject(ObjectOutputStream fos) throws IOException {
        System.out.println("writeObject...");
    }
}
//HelloInterface.java
import java.rmi.Remote;

public interface HelloInterface extends java.rmi.Remote {
    public void sayHello(Message from) throws java.rmi.RemoteException;
}
//HelloImpl.java
import javax.rmi.PortableRemoteObject;

public class HelloImpl extends PortableRemoteObject implements HelloInterface {
    public HelloImpl() throws java.rmi.RemoteException {
        super();     // invoke rmi linking and remote object initialization
    }

    public void sayHello(Message from) throws java.rmi.RemoteException {
        System.out.println("Hello from " + from + "!!");
        System.out.flush();
    }
}

编写服务端并把上面这三个java文件拷贝到一个java项目里面

//HelloServer.java
import javax.naming.Context;
import javax.naming.InitialContext;
import java.util.Hashtable;

public class HelloServer {

    public static void main(String[] args) {
        try {
            // Step 1: Instantiate the Hello servant
            HelloImpl helloRef = new HelloImpl();

            // Step 2: Publish the reference in the Naming Service
            // using JNDI API
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.cosnaming.CNCtxFactory");
            env.put(Context.PROVIDER_URL, "iiop://127.0.0.1:1050");
            Context initialNamingContext = new InitialContext(env);
            initialNamingContext.rebind("HelloService", helloRef);

            System.out.println("Hello Server: Ready...");

        } catch (Exception e) {
            System.out.println("Trouble: " + e);
            e.printStackTrace();
        }
    }

}

直接启动HelloServer的main方法会在target目录编译好代码

在项目的target/classes目录下执行rmic命令生成服务端和客户端的代理类

rmic -iiop org.example.HelloImpl

上面的命令会创建以下文件

_ HelloImpl_Tie.class 用于服务端

_ HelloInterface_Stub.class 用户客户端

启动服务端

# windows系统执行
start orbd -ORBInitialPort 1050
# linux系统执行
orbd -ORBInitialPort 1050 &

用IDEA编辑器直接启动HelloServer的main方法即可启动成功

在创建一个新的客户端项目去启动客户端代码.

import javax.rmi.*;
import java.util.Hashtable;
import javax.naming.InitialContext;

public class HelloClient {
    public static void main(String args[]) {
        try {
            Hashtable<String, String> env = new Hashtable<String, String>();
            env.put("java.naming.factory.initial", "com.sun.jndi.cosnaming.CNCtxFactory");
            env.put("java.naming.provider.url", "iiop://localhost:1050");

            InitialContext ic = new InitialContext(env);

            // STEP 1: Get the Object reference from the Name Service
            // using JNDI call.
            Object 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.

            HelloInterface hi = (HelloInterface) PortableRemoteObject.narrow(
                    objref, HelloInterface.class);

            Message message = new Message();
            hi.sayHello(message);

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

启动步骤和服务端步骤除了不执行orbd命令其它步骤都一样.

用wireshark抓包看下

传输的数据中没有aced魔术头,这里不是使用ObjectOutputStream序列化的数据。在客户端和服务端开始位置都DEBUG看下。

先列出客户端的调用栈

writeObject:12, Message
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeObjectWriter:624, IIOPOutputStream (com.sun.corba.se.impl.io)
outputObject:590, IIOPOutputStream (com.sun.corba.se.impl.io)
simpleWriteObject:174, IIOPOutputStream (com.sun.corba.se.impl.io)
writeValueInternal:236, ValueHandlerImpl (com.sun.corba.se.impl.io)
writeValueWithVersion:218, ValueHandlerImpl (com.sun.corba.se.impl.io)
writeValue:150, ValueHandlerImpl (com.sun.corba.se.impl.io)
writeRMIIIOPValueType:807, CDROutputStream_1_0 (com.sun.corba.se.impl.encoding)
write_value:856, CDROutputStream_1_0 (com.sun.corba.se.impl.encoding)
write_value:870, CDROutputStream_1_0 (com.sun.corba.se.impl.encoding)
write_value:665, CDROutputStream_1_0 (com.sun.corba.se.impl.encoding)
write_value:250, CDROutputStream (com.sun.corba.se.impl.encoding)
sayHello:-1, _HelloInterface_Stub
main:26, HelloClient

_HelloInterface_Stub这个class是执行rmic -iiop命令生成的

sayHello方法会从request中获取数据,然后执行CDROutputStream.write_value方法会执行到IIOPOutputStream.invokeObjectWriter方法

这里反射调用Message.writeObject方法,方法参数属性是IIOPOutputStream类型,这也解释了为什么前面抓包数据中没有aced魔术头。

下面是服务端的调用栈

readObject:8, Message
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeObjectReader:1722, IIOPInputStream (com.sun.corba.se.impl.io)
inputObject:1240, IIOPInputStream (com.sun.corba.se.impl.io)
simpleReadObject:416, IIOPInputStream (com.sun.corba.se.impl.io)
readValueInternal:341, ValueHandlerImpl (com.sun.corba.se.impl.io)
readValue:307, ValueHandlerImpl (com.sun.corba.se.impl.io)
read_value:999, CDRInputStream_1_0 (com.sun.corba.se.impl.encoding)
read_value:271, CDRInputStream (com.sun.corba.se.impl.encoding)
_invoke:-1, _HelloImpl_Tie
dispatchToServant:654, CorbaServerRequestDispatcherImpl (com.sun.corba.se.impl.protocol)
dispatch:205, CorbaServerRequestDispatcherImpl (com.sun.corba.se.impl.protocol)
handleRequestRequest:1700, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)
handleRequest:1558, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)
handleInput:940, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)
callback:198, RequestMessage_1_2 (com.sun.corba.se.impl.protocol.giopmsgheaders)
handleRequest:712, CorbaMessageMediatorImpl (com.sun.corba.se.impl.protocol)
dispatch:474, SocketOrChannelConnectionImpl (com.sun.corba.se.impl.transport)
doWork:1237, SocketOrChannelConnectionImpl (com.sun.corba.se.impl.transport)
performWork:490, ThreadPoolImpl$WorkerThread (com.sun.corba.se.impl.orbutil.threadpool)
run:519, ThreadPoolImpl$WorkerThread (com.sun.corba.se.impl.orbutil.threadpool)

服务端也是利用rmic命令生成_HelloImpl_Tie的class

_HelloImpl_Tie有一个_invoke方法,读取流中的数据去执行CDRInputStream.read_value方法。然后会执行IIOPInputStream.invokeObjectReader方法,这个方法会反射执行Message.readObject方法,参数属性也是IIOPInputStream类型。

到这里就可以理解了,RMI-IIOP协议中使用IIOPOutputStream去序列化数据,字节流与ObjectOutputStream序列化数据不太一样。

发送poc看下weblogic调用链.

lookup:417, InitialContext (javax.naming)
doInContext:132, JndiTemplate$1 (com.bea.core.repackaged.springframework.jndi)
execute:88, JndiTemplate (com.bea.core.repackaged.springframework.jndi)
lookup:130, JndiTemplate (com.bea.core.repackaged.springframework.jndi)
lookup:155, JndiTemplate (com.bea.core.repackaged.springframework.jndi)
lookupUserTransaction:565, JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta)
initUserTransactionAndTransactionManager:444, JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta)
readObject:1198, JtaTransactionManager (com.bea.core.repackaged.springframework.transaction.jta)
invoke:-1, GeneratedMethodAccessor30 (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
readObject:315, ObjectStreamClass (weblogic.utils.io)
readValueData:281, ValueHandlerImpl (weblogic.corba.utils)
readValue:93, ValueHandlerImpl (weblogic.corba.utils)
read_value:2128, IIOPInputStream (weblogic.iiop)
read_value:1936, IIOPInputStream (weblogic.iiop)
read_value_internal:220, AnyImpl (weblogic.corba.idl)
read_value:115, AnyImpl (weblogic.corba.idl)
read_any:1648, IIOPInputStream (weblogic.iiop)
read_any:1641, IIOPInputStream (weblogic.iiop)
_invoke:58, _NamingContextAnyImplBase (weblogic.corba.cos.naming)
invoke:249, CorbaServerRef (weblogic.corba.idl)
invoke:230, ClusterableServerRef (weblogic.rmi.cluster)
run:522, BasicServerRef$1 (weblogic.rmi.internal)
doAs:363, AuthenticatedSubject (weblogic.security.acl.internal)
runAs:146, SecurityManager (weblogic.security.service)
handleRequest:518, BasicServerRef (weblogic.rmi.internal)
run:118, WLSExecuteRequest (weblogic.rmi.internal.wls)
execute:263, ExecuteThread (weblogic.work)
run:221, ExecuteThread (weblogic.work)

断点几个关键的地方

_NamingContextAnyImplBase.invoke方法中请求类型是bind_any,对应var5的值为0,会执行var2.read_any(),var2的值是IIOPInputStream.

会去执行AnyImpl.read_value方法

var2.kind().value()值是29会执行IIOPInputStream.read_value方法。

var14是JtaTransactionManager对象,var13是JtaTransactionManager类的序列化描述符,会执行ValueHandlerImpl.readValue方法

这里执var2是ObjectStreamClass类,var1是JtaTransactionManager对象,var6是JtaTransactionManager对象输入流数据,会执行ObjectStreamClass.readObject方法

这里会反射调用,JtaTransactionManager.readObject方法。

调试到这里就明白了,那看下weblogic怎么修复的。

下载并安装此补丁后,在发送poc会提示。

断点看下这里

可以看到JtaTransactionManager父类AbstractPlatformTransactionManager在黑名单列表里面,第一个断点处verifyClassPermitted方法的for循环会循环判断父类,所以会抛出黑名单异常警告。


Paper 本文由 Seebug Paper 发布,如需转载请注明来源。本文地址:https://paper.seebug.org/1138/



文章来源: https://paper.seebug.org/1138/
如有侵权请联系:admin#unsafe.sh