WebLogic CVE-2021-2135分析及POC构造遇到的问题
文章首发于安全客:https://www.anquanke.com/post/id/248770。
一、前言
早在今年4月 Weblogic发布了安全公告,里面有一个编号是CVE-2021-2135的反序列化漏洞,因为工作原因需要构造该漏洞POC,当时拿到了安全补丁,但是奈何太菜并没有解出来。后来360CERT发布了分析文章,花了一周多的时间终于把POC构造出来了。现在把分析学习及POC构造中遇到的问题记录下来。
4月与1月补丁WebLogicFilterConfig.class
diff后如下,将com.tangosol.internal.util.SimpleBinaryEntry
加入了黑名单。
1 | private static final String[] DEFAULT_BLACKLIST_CLASSES = new String[]{"oracle.jdbc.pool.OraclePooledConnection"}; |
根据补丁反向推漏洞的话主要两部分命令执行载体和反序列化载体,命令执行载体达到我们执行命令的目的,反序列化载体从反序列化从入口到命令执行载体入口的动态执行,我们先分别来分析下。
二、命令执行载体分析:
既然我们已经知道com.tangosol.internal.util.SimpleBinaryEntry
在黑名单里,那么我们来分析下com.tangosol.internal.util.SimpleBinaryEntry
的特性
1、SimpleBinaryEntry的特性
SimpleBinaryEntry
实现了SerializerAware
、ExternalizableLite
、PortableObject
,ExternalizableLite
用来处理序列化相关数据。
1 | public class SimpleBinaryEntry<K, V> |
SimpleBinaryEntry
有2个可序列化属性和3个不可序列化属性,其中m_binKey
和m_binValue
是二进制类型。
1 | "binKey") ( |
SimpleBinaryEntry
有参构造方法通过传参BinaryEntry
或者key和value给属性m_binKey
和m_binValue
赋值:
1 | public SimpleBinaryEntry(Binary binKey, Binary binValue) { |
SimpleBinaryEntry
借助m_serializer
通过getKey
和getValue
方法处理另外两个属性m_key
和m_value
。当其没有被赋值,会调用ExternalizableHelper.fromBinary
来处理并返回结果,这时候的m_key
和m_value
处理后可以是指定类型的实例,而这也是漏洞的关键之处,我们可以通过构造将m_key
或m_value
转为我们想要的对象。
1 | public K getKey() { |
SimpleBinaryEntry重写了toString方法,我们可以通过该方法调用getKey。
1 | public String toString() { |
我们来看下ExternalizableHelper.fromBinary
。
2、ExternalizableHelper.fromBinary
ExternalizableHelper.fromBinary
最终会调用ExternalizableHelper.deserializeInternal
处理,deserializeInternal
方法如下,当nType!=21
时调用ExternalizableHelper.readObjectInternal
读取对象。
1 | // serializer可以是SimpleBinaryEntry.this.m_serializer,buf可以是SimpleBinaryEntry.m_binKey或m_binValue |
ExternalizableHelper.readObjectInternal
会按照nType
的值进行处理,根据ExternalizableHelper.writeObjectnType
,在序列化时通过getStreamFormat
进行赋值。
1 | // getStreamFormat:o instanceof ExternalizableLite ? 10,当对象是ExternalizableLite实例,nType=10 |
ExternalizableHelper.readObjectInternal
代码如下,根据序列化数据的类型nType
进行反序列化读取对象,当nType=10,调用readExternalizableLite。
1 | private static Object readObjectInternal(DataInput in, int nType, ClassLoader loader) throws IOException { |
在ExternalizableHelper.readExternalizableLite
中,会实例化类为value,并且会调用value所属类的readExternal方法来读取最终的对象。
1 | public static ExternalizableLite readExternalizableLite(DataInput in, ClassLoader loader) throws IOException { |
看到这里我们已经明白了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
执行命令。
1 | ExternalizableHelper.readExternalizableLite() |
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添加数据。
1 | public void readExternal(DataInput in) throws IOException { |
TreeMap.put()调用TreeMap.compare,最终会调用comparator.compare((K)k1, (K)k2)
。
1 | public V put(K key, V value) { |
如果这里comparator是我们构造的MvelExtractor实例,MvelExtractor继承了AbstractExtractor,便可以调用AbstractExtractor.compare
,从而调用MvelExtractor.extract
达到命令执行的目的。
1 | # AbstractExtractor.compare |
三、反序列化载体分析:
1、SimpleBinaryEntry.toString()
的触发
反序列化到SimpleBinaryEntry.toString()
的触发,在360CERT中提到用com.sun.org.apache.xpath.internal.objects.XString.equals()
。当XString.equals()
的参数是SimpleBinaryEntry实例时,可以调用SimpleBinaryEntry.toString
。
1 | public boolean equals(Object obj2) |
这时候需要触发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>
。
1 | public V put(K key, V value) { |
2、反序列化入口
那么接下来是找到反序列化入口,必然涉及到readObject或readExternal等类似读取的方法,如何在这些方法中调用LiteMap.put
。com.tangosol.util.processor.ConditionalPutAll
实现了com.tangosol.io.ExternalizableLite
并重写了readExternal,在readExternal中构造了LiteMap实例,并且调用了com.tangosol.util.ExternalizableHelper.readMap
将map的属性读取赋值给LiteMap实例。
1 | public void readExternal(DataInput in) throws IOException { |
com.tangosol.util.ExternalizableHelper.readMap
中调用了map.put
,假如oKey=xString、oVal=simpleBinaryEntry,刚好就能调用这里的map是LiteMap的实例,所以会调用com.tangosol.util.InflatableMap.put(xString,simpleBinaryEntry)
,跟前面的链就连上了。
1 | public static int readMap(DataInput in, Map map, ClassLoader loader) throws IOException { |
刚开始的时候分析到这里我以为就完成了整个链,没想到忽略了一点-ConditionalPutAll只有readExternal(DataInput in)
方法,没有实现Externalizable,不能传入ObjectInput
,所以不但能作为入口。我们可以用CVE-2020-14756的com.tangosol.coherence.servlet.AttributeHolder
作为入口,AttributeHolder实现了Externalizable,并且可以通过调用readExternal(ObjectInput in)
调用ExternalizableHelper.readObject(in)
1 | public void readExternal(ObjectInput in) throws IOException { |
四、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解决了。
1 | 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
,所以我在设置时也用了它。
1 | Serializer m_serializer= new DefaultSerializer(SimpleBinaryEntry.class.getClassLoader()); |
3、ClassCastException报错问题
在用y4er CVE-2020-14756的POC时也报了错:ClassCastException,但不影响执行命令,但是我的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。
1 | MvelExtractor extractor1 = new MvelExtractor("java.lang.Runtime.getRuntime().exec(\"calc\");return new Integer(1);"); |
参考链接:
https://xz.aliyun.com/t/9550
https://y4er.com/post/weblogic-cve-2020-14756/
https://mp.weixin.qq.com/s/eyZfAPivCkMbNCfukngpzg