WebLogic CVE--2551漏洞分析

作者: 天融信阿尔法实验室

原文链接: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循环会循环判断父类,所以会抛出黑名单异常警告。

以上是 WebLogic CVE--2551漏洞分析 的全部内容, 来源链接: utcz.com/p/199532.html

回到顶部