WebLogic CVE-2021-2394和CVE-2020-14841分析

CVE-2021-2394 Gadgets 分析

1
2
3
4
5
6
7
8
9
10
11
12
13
com.tangosol.coherence.servlet.AttributeHolder.readExternal
com.tangosol.util.aggregator.TopNAggregator$PartialResult.readExternal
com.tangosol.util.aggregator.TopNAggregator$PartialResult.add
com.tangosol.util.SortedBag.add
java.util.TreeMap.put
java.util.TreeMap.compare
com.tangosol.util.SortedBag$WrapperComparator.compare
com.tangosol.util.extractor.AbstractExtractor.compare
oracle.eclipselink.coherence.integrated.internal.querying.FilterExtractor.extract
org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor.getAttributeValueFromObject
java.lang.reflect.Method.invoke
com.sun.rowset.JdbcRowSetImpl.connect
javax.naming.InitialContext.lookup

从入口到FilterExtractor.extract调用不再说了,跟之前CVE-2021-2135类似,通过InitialContext.lookup到ldap执行命令过程可以看下之前的分析。这里主要分析下FilterExtractor.extractMethod.invoke及POC构造问题。

FilterExtractor.extract中调用了attributeAccessor属性的initializeAttributes和getAttributeValueFromObject方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object extract(Object obj) {
if (obj instanceof Wrapper) {
obj = ((Wrapper)obj).unwrap();
}

if (!this.attributeAccessor.isInitialized()) {
// 调用attributeAccessor属性的initializeAttributes方法
this.attributeAccessor.initializeAttributes(obj.getClass());
}

try {
// 调用attributeAccessor属性的getAttributeValueFromObject方法
return this.attributeAccessor.getAttributeValueFromObject(obj);
} catch (Exception var3) {
return new FilterExtractor.InvalidObject();
}
}

如果让attributeAccessor属性是MethodAttributeAccessor实例,那么就可以调用MethodAttributeAccessor.getAttributeValueFromObject,当不满足PrivilegedAccessHelper.shouldUsePrivilegedAccess()能够进一步调用invoke方法,这里注意getAttributeValueFromObject(anObject, (Object[])null)传入的参数是null,那么invoke调用的就是无参方法。当传入的anObject是JdbcRowSetImpl实例,this.getMethod是connect方法,则可以调用JdbcRowSetImpl.connect

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public Object getAttributeValueFromObject(Object anObject) throws DescriptorException {
return this.getAttributeValueFromObject(anObject, (Object[])null);
}

protected Object getAttributeValueFromObject(Object anObject, Object[] parameters) throws DescriptorException {
try {
if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
......
} else {
// 当传入的anObject是JdbcRowSetImpl实例,this.getMethod是connect方法,则可以调用JdbcRowSetImpl.connect
return this.getMethod.invoke(anObject, parameters);
}
} catch (IllegalArgumentException var6) {
......
}
}

getMethod属性本身是不可以序列化的,但是在反序列化时可以调用到MethodAttributeAccessor.initializeAttributes来完成赋值工作。initializeAttributes中使用Helper.getDeclaredMethod和getGetMethodName属性获取到Method对象并调用setGetMethod赋值给getMethod属性,而getMethodName属性是可以序列化的,在构造POC时可以设置为connect。

另外有一点值得注意:this.isWriteOnly()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public void initializeAttributes(Class theJavaClass) throws DescriptorException {
this.initializeAttributes(theJavaClass, (Class[])null);
}

protected void initializeAttributes(Class theJavaClass, Class[] getParameterTypes) throws DescriptorException {
if (this.getAttributeName() == null) {
throw DescriptorException.attributeNameNotSpecified();
} else {
DescriptorException descriptorException;
try {
// Helper.getDeclaredMethod通过Class对象、方法名及参数获取一个Method对象
this.setGetMethod(Helper.getDeclaredMethod(theJavaClass, this.getGetMethodName(), getParameterTypes));
if (!this.isWriteOnly()) {
//默认isWriteOnly是false,会调用setSetMethod
this.setSetMethod(Helper.getDeclaredMethod(theJavaClass, this.getSetMethodName(), this.getSetMethodParameterTypes()));
}

} catch (NoSuchMethodException var5) {
......
}
}
}

这里需要注意,当isWriteOnly属性是false时会调用setSetMethod。在CVE-2020-14841中思路是将该属性直接设置为false,从而避免调用setSetMethod:

1
2
3
4
MethodAttributeAccessor methodAttributeAccessor = new MethodAttributeAccessor();
methodAttributeAccessor.setGetMethodName("getDatabaseMetaData");
methodAttributeAccessor.setIsWriteOnly(true);
methodAttributeAccessor.setAttributeName("xxx");

但是CVE-2021-2934不能直接这样,在360cert中说明了这一点:因为MethodAttributeAccessor被加入了黑名单(笔者未验证),所以思路是可以调用setSetMethod方法,我们在构造POC时需要设置setMethodName。

如何设置setMethodName?在initializeAttributes方法中,将this.getSetMethodParameterTypes()返回值作为方法的参数,最终会获取到this.getMethod代表的方法的返回值参数列表,因此我们需要关注下JdbcRowSetImpl.connect的返回值,connect会返回一个Connection对象,setConnection刚好以connection为参数,所以我们就可以设置setMethodName为setConnection。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
protected Class[] getSetMethodParameterTypes() {
// 返回getGetMethod方法的返回值类型的Class对象
return new Class[]{this.getGetMethodReturnType()};
}

public Class getGetMethodReturnType() throws DescriptorException {
if (this.getGetMethod() == null && this.getGetMethodName() != null && this.getGetMethodName().indexOf("_persistence_") > -1) {
AbstractSessionLog.getLog().log(1, "no_weaved_vh_method_found_verify_weaving_and_module_order", this.getGetMethodName(), (Object)null, this);
throw DescriptorException.nullPointerWhileGettingValueThruMethodAccessorCausedByWeavingNotOccurringBecauseOfModuleOrder(this.getGetMethodName(), "", (Throwable)null);
} else if (PrivilegedAccessHelper.shouldUsePrivilegedAccess()) {
try {
return (Class)AccessController.doPrivileged(new PrivilegedGetMethodReturnType(this.getGetMethod()));
} catch (PrivilegedActionException var1) {
return null;
}
} else {
// 返回getGetMethod方法的返回值类型
return PrivilegedAccessHelper.getMethodReturnType(this.getGetMethod());
}
}

当然,除了connect和setConnection还可以有prepare和setPreparedStatement:

1
2
methodAttributeAccessor.setGetMethodName("prepare");
methodAttributeAccessor.setSetMethodName("setPreparedStatement");

既然上面提到了CVE-2020-14841,也顺便分析下。

CVE-2020-14841

CVE-2020-14841的Gadget:

1
2
3
4
5
6
7
8
9
10
java.util.PriorityQueue.readObject
java.util.PriorityQueue.heapify
java.util.PriorityQueue.siftDown
java.util.PriorityQueue.siftDownUsingComparator
com.tangosol.util.comparator.ExtractorComparator.compare
oracle.eclipselink.coherence.integrated.internal.cache.LockVersionExtractor.extract
org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor.getAttributeValueFromObject
java.lang.reflect.Method.invoke
com.sun.rowset.JdbcRowSetImpl.connect
javax.naming.InitialContext.lookup

MethodAttributeAccessor.getAttributeValueFromObjectjavax.naming.InitialContext.lookup上面已经分析过了,不再赘述,主要分析下PriorityQueue.readObject到``LockVersionExtractor.extract`。

PriorityQueue是一个优先队列,本质是数组,在其内部定义了readObject,可自己处理序列化和反序列化。PriorityQueue反序列化时特点是先创建一个数组queue,然后再调用PriorityQueue.heapify进行排序。当内部属性Comparator有被赋值,处理排序时会通过PriorityQueue.heapify调用PriorityQueue.siftDownUsingComparator,节点之间用比较器Comparator的compare方法进行比较。所以当Comparator被赋值为ExtractorComparator,反序列化时会调用ExtractorComparator.compare。

ExtractorComparator.compare如下,内部调用了extract方法,当参数不是Entry实例,执行this.m_extractor.extract,构造ExtractorComparator的m_extractor属性为LockVersionExtractor实例,就能调用LockVersionExtractor.extract。LockVersionExtractor实现了ValueExtractor和ExternalizableLite,刚好满足序列化要求。

1
2
3
4
5
6
7
8
9
10
public int compare(T o1, T o2) {
// o1 not instanceof Entry -> this.m_extractor.extract
Comparable a1 = o1 instanceof Entry ? (Comparable)((Entry)o1).extract(this.m_extractor) : (Comparable)this.m_extractor.extract(o1);
Comparable a2 = o2 instanceof Entry ? (Comparable)((Entry)o2).extract(this.m_extractor) : (Comparable)this.m_extractor.extract(o2);
if (a1 == null) {
return a2 == null ? 0 : -1;
} else {
return a2 == null ? 1 : a1.compareTo(a2);
}
}

LockVersionExtractor.extract方法如下,和FilterExtractor.extract一样,当arg0参数是JdbcRowSetImpl实例,最终可以调用JdbcRowSetImpl.connect。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public Object extract(Object arg0) {
if (arg0 == null) {
return null;
} else {
if (arg0 instanceof Wrapper) {
arg0 = ((Wrapper)arg0).unwrap();
}

if (!this.accessor.isInitialized()) {
this.accessor.initializeAttributes(arg0.getClass());
}

return this.accessor.getAttributeValueFromObject(arg0);
}
}

看了下CVE-2020-14654,其实和CVE-2020-14841一样的,只不过LockVersionExtractor变成了UniversalExtractor,UniversalExtractor.extract中满足条件可调用invoke,类似的还有CVE-2020-2555。

参考链接:

https://cert.360.cn/report/detail?id=2d3b4207433c18ddf6d9607a57f92960
https://mp.weixin.qq.com/s/wFHhWvnCLm1xcWZIbv6O3A
https://blog.csdn.net/kobejayandy/article/details/46832797