JAVA反序列化漏洞完整過程分析與除錯

wyzsk發表於2020-08-19
作者: z_zz_zzz · 2016/03/04 11:08

0x00 前言


關於JAVA的Apache Commons Collections元件反序列漏洞的分析文章已經有很多了,當我看完很多分析文章後,發現JAVA反序列漏洞的一些要點與細節未被詳細描述,還需要繼續分析之後才能更進一步理解並掌握這個漏洞。

上述的要點與細節包括:

  1. 為什麼需要使用JAVA反射機制
  2. 為什麼需要利用sun.reflect.annotation.AnnotationInvocationHandler類
  3. 為什麼呼叫TransformedMap類的decorate方法時,引數一的Map物件需要put進"value"與非空的值*
  4. 為什麼AnnotationInvocationHandler類的例項化引數一需要為java.lang.annotation.Retention類

為了方便和我一樣的小白們理解這個漏洞,我將JAVA反序列化漏洞完整過程的分析與除錯進行了整理。分析過程中利用的類為TransformedMap與AnnotationInvocationHandler。發現漏洞不是我等小白能力所及,因此本文不以挖掘漏洞的角度來進行分析,而是在已知漏洞存在的情況下分析漏洞。

0x01 基礎知識


JAVA序列化與反序列化

JAVA序列化簡介

為了分析JAVA的反序列化漏洞,首先需要了解JAVA的序列化與反序列化機制。

以下內容來自JDK1.6 API文件中對ObjectOutputStream的說明。

ObjectOutputStream 將 Java 物件的基本資料型別和圖形寫入 OutputStream。可以使用 ObjectInputStream 讀取(重構)物件。透過在流中使用檔案可以實現物件的持久儲存。如果流是網路套接字流,則可以在另一臺主機上或另一個程式中重構物件。

只能將支援 java.io.Serializable 介面的物件寫入流中。每個 serializable 物件的類都被編碼,編碼內容包括類名和類簽名、物件的欄位值和陣列值,以及從初始物件中引用的其他所有物件的閉包。

writeObject 方法用於將物件寫入流中。所有物件(包括 String 和陣列)都可以透過 writeObject 寫入。可將多個物件或基元寫入流中。必須使用與寫入物件時相同的型別和順序從相應 ObjectInputstream 中讀回物件。

即使用ObjectOutputStream.writeObject方法可對實現了Serializable介面的物件進行序列化,序列化後的資料可儲存在檔案中,或透過網路傳輸。

JAVA反序列化簡介

以下內容來自JDK1.6 API文件中對ObjectInputStream的說明。

ObjectInputStream 對以前使用 ObjectOutputStream 寫入的基本資料和物件進行反序列化。

ObjectOutputStream 和 ObjectInputStream 分別與 FileOutputStream 和 FileInputStream 一起使用時,可以為應用程式提供對物件圖形的持久儲存。ObjectInputStream 用於恢復那些以前序列化的物件。其他用途包括使用套接字流在主機之間傳遞物件,或者用於編組和解組遠端通訊系統中的實參和形參。

ObjectInputStream 確保從流建立的圖形中所有物件的型別與 Java 虛擬機器中顯示的類相匹配。使用標準機制按需載入類。

只有支援 java.io.Serializable 或 java.io.Externalizable 介面的物件才能從流讀取。

readObject 方法用於從流讀取物件。應該使用 Java 的安全強制轉換來獲取所需的型別。在 Java 中,字串和陣列都是物件,所以在序列化期間將其視為物件。讀取時,需要將其強制轉換為期望的型別。

即使用ObjectInputStream.readObject方法可對序列化的資料進行反序列化。當實現了Serializable介面的物件被反序列化時,該物件的readObject方法會被呼叫。

對JAVA基礎類的序列化與反序列化測試

String實現了Serializable介面,可進行序列化。

以下測試程式碼會對String類的物件進行序列化,將序列化的資料儲存在檔案中,再從檔案讀取序列化的資料進行反序列化。執行上述程式碼後,能夠正確輸出原String類的物件的值。

JAVA序列化資料的magic number

java.io.ObjectStreamConstants類中定義了STREAM_MAGIC與STREAM_VERSION,檢視JDK1.5、1.6、1.7、1.8的ObjectStreamConstants類,STREAM_MAGIC值均為0xaced,STREAM_VERSION值均為5。JDK1.6的原始碼中,上述變數的程式碼如下。

#!java
package java.io;

/**
 * Constants written into the Object Serialization Stream. 
 *
 * @author  unascribed
 * @version %I%, %G%
 * @since JDK 1.1
 */
public interface ObjectStreamConstants {

/**
 * Magic number that is written to the stream header.
 */
final static short STREAM_MAGIC = (short)0xaced;

/**
 * Version number that is written to the stream header.
 */
final static short STREAM_VERSION = 5;

即0xaced為JAVA物件序列化流的魔數,0x0005為JAVA物件序列化的版本號,JAVA物件序列化資料的前4個位元組為“AC ED 00 05”。

檢視上一步驟生成的儲存了序列化資料的檔案,檔案內容開頭為“AC ED 00 05”,與上述描述相符。

對自定義類的序列化與反序列化測試

以下測試程式碼為test.SerializeMyClass類,在其中定義了內部類MyObject。MyObject類實現了Serializable介面,SerializeMyClass類會對MyObject類的物件進行序列化,將序列化的資料儲存在檔案中,再從檔案讀取序列化的資料進行反序列化。

執行結果如下

#!bash
MyObject(String name) tttest
MyObject-readObject!!!!!!!!!!!!!! tttest
tttest!  

可以看到MyObject類實現的Serializable介面的readObject方法會被呼叫,且物件被序列化再反序列化後,對其值不影響。

生成的儲存了序列化資料的檔案,檔案內容開頭也為“AC ED 00 05”,可以看到檔案內容包含了包名與類名、類中包含的變數名稱、型別及變數的值。

JAVA反射機制

使用JAVA反射機制呼叫FileOutputStream類寫檔案

呼叫FileOutputStream類寫檔案時,常用的程式碼如下:

#!java
FileOutputStream fos = new FileOutputStream("1.txt");
fos.write("abc".getBytes());

若需要使用JAVA反射機制呼叫FileOutputStream類寫檔案,且只允許呼叫Class.getMethod與Method.invoke方法,上述程式碼需修改為如下形式。

使用JAVA反射機制呼叫Runtime類執行程式

呼叫Runtime類執行程式時,常用的程式碼如下:

#!java
Runtime runtime = Runtime.getRuntime();
runtime.exec("calc");

若需要使用JAVA反射機制呼叫Runtime類執行程式件,且只允許呼叫Class.getMethod與Method.invoke方法,上述程式碼需修改為如下形式。

JAVA反射機制與序列化

當需要操作無法直接訪問的類時,需要使用JAVA的反射機制。即對無法直接訪問的類進行序列化時,需要使用JAVA的反射機制。

以下測試程式碼為testReflection.TestReflection類,與前文中的test.MyObject類不在同一個包中,在TestReflection類中對MyObject類進行序列化時,需要使用JAVA的反射機制。

以下為執行結果,可以看到使用JAVA的反射機制後,能夠對無法直接訪問的類進行序列化。

#!bash
-before newInstance-  
MyObject(String name) tttest  
-after newInstance-  
byteOut.toByteArray().length:71  
MyObject-readObject!!!!!!!!!!!!!! tttest  
object:class test.MyObject  

-before newInstance-  
MyObject(String name) no name-default  
-after newInstance-  
byteOut.toByteArray().length:80  
MyObject-readObject!!!!!!!!!!!!!! no name-default  
object:class test.MyObject  

0x02 漏洞分析


使用JAVA反序列化的場景

breenmachine在“What Do WebLogic, WebSphere, JBoss, Jenkins, OpenNMS, and Your Application Have in Common This Vulnerability”中列出了以下會使用JAVA反序列化的場景。

Java LOVES sending serialized objects all over the place. For example:

In HTTP requests – Parameters, ViewState, Cookies, you name it.
RMI – The extensively used Java RMI protocol is 100% based on serialization.
RMI over HTTP – Many Java thick client web apps use this – again 100% serialized objects.
JMX – Again, relies on serialized objects being shot over the wire.
Custom Protocols – Sending an receiving raw Java objects is the norm – which we’ll see in some of the exploits to come.

可能存在JAVA反序列化漏洞的場景

JAVA中介軟體通常透過網路接收客戶端傳送的序列化資料,JAVA中介軟體在對序列化資料進行反序列化資料時,會呼叫被序列化物件的readObject方法。如果某個物件的readObject方法中能夠執行任意程式碼,那麼JAVA中介軟體在對其進行反序列化時,也會執行對應的程式碼。如果能夠找到滿足上述條件的物件進行序列化併傳送給JAVA中介軟體,JAVA中介軟體也會執行指定的程式碼,即存在反序列化漏洞。

JAVA反序列化漏洞需要滿足兩個條件:

  1. JAVA中件間需要存在客戶端進行序列化時使用的類,否則伺服器在進行反序列化時會出現ClassNotFoundException異常;
  2. 客戶端選擇的進行序列化的類在執行程式碼時,不會進行任何驗證或限制,會完全按照要求執行。

利用JAVA反序列化漏洞可以使伺服器執行任意程式碼,可以直接控制伺服器,危害非常大。

Apache Commons Collections元件說明

下文中出現的以下類均包含在Apache Commons Collections元件中。

org.apache.commons.collections.functors.ConstantTransformer
org.apache.commons.collections.functors.InvokerTransformer
org.apache.commons.collections.functors.ChainedTransformer
org.apache.commons.collections.map.TransformedMap
org.apache.commons.collections.map.AbstractInputCheckedMapDecorator
org.apache.commons.collections.map.AbstractMapDecorator
org.apache.commons.collections.set.AbstractSetDecorator
org.apache.commons.collections.collection.AbstractCollectionDecorator
org.apache.commons.collections.iterators.AbstractIteratorDecorator
org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator

Apache Commons Collections元件原生的jar包為commons-collections-xxx.jar。

本文中分析的commons-collections-xxx.jar版本為3.2.1,JDK版本為1.6。

透過對commons-collections-xxx.jar中涉及的程式碼進行反編譯,增加輸出或進行除錯,可以跟蹤漏洞觸發時的程式碼執行情況。

利用ChainedTransformer執行程式碼

ConstantTransformer類的transform方法

org.apache.commons.collections.functors.ConstantTransformer類的transform方法會返回建構函式傳入的引數。ConstantTransformer類相關程式碼如下。

#!java
public class ConstantTransformer implements Transformer, Serializable {

    private final Object iConstant;

    public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }

    public Object transform(Object input) {
        return this.iConstant;
    }
    ...
}

InvokerTransformer類的transform方法

org.apache.commons.collections.functors.InvokerTransformer類的transform方法可以透過JAVA反射機制執行指定的程式碼,能指定所需執行的類、方法及引數,且在transform方法中未進行任何驗證或限制。transform方法中執行的程式碼的方法名、引數型別及引數值在InvokerTransformer類的建構函式中指定。InvokerTransformer類相關程式碼如下。

#!java
public class InvokerTransformer implements Transformer, Serializable {

    private final String iMethodName;
    private final Class[] iParamTypes;
    private final Object[] iArgs;

    public InvokerTransformer(String methodName, Class[] paramTypes,
        Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }       

    public Object transform(Object input) {
        if (input == null)
            return null;
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
            return method.invoke(input, this.iArgs);
        }
        ...
    }
}

利用ChainedTransformer執行程式碼分析

以下為利用org.apache.commons.collections.functors.ChainedTransformer類執行任意程式碼的示例,當執行最後的“chain.transform(chain);”後,會執行傳入的Transformer陣列指定的程式碼。在該示例中,會啟動計算器程式。

ConstantTransformer與InvokerTransformer陣列可被轉換為org.apache.commons.collections.functors.ChainedTransformer物件。在ChainedTransformer類的帶引數建構函式中,會將引數中的ConstantTransformer與InvokerTransformer陣列儲存為this.iTransformers物件。在ChainedTransformer類的transform方法中,會依次呼叫this.iTransformers對應的ConstantTransformer與InvokerTransformer陣列的transform方法,且前一次執行transform方法的返回值object,會作為下一次執行transform方法的引數object。ChainedTransformer類的相關程式碼如下。

#!java
public class ChainedTransformer implements Transformer, Serializable {

    public ChainedTransformer(Transformer[] transformers) {
        this.iTransformers = transformers;
    }
    ...
    public Object transform(Object object) {
        for (int i = 0; i < this.iTransformers.length; ++i) {
            object = this.iTransformers[i].transform(object);
        }
        return object;
    }
    ...
}

對於上述的示例程式碼,在執行最後的“chain.transform(chain);”方法時,會首先呼叫ConstantTransformer.transform方法獲取其建構函式中傳入的類,再依次呼叫InvokerTransformer.transform方法執行其建構函式中傳入的方法,等價於下面的程式碼。

上述程式碼與前文“使用JAVA反射機制呼叫Runtime類執行程式”中的程式碼相同,已經過驗證可以成功執行,能夠呼叫指定的程式。ChainedTransformer也能夠呼叫FileOutputStream類進行寫檔案操作,相關程式碼見前文“使用JAVA反射機制呼叫FileOutputStream類寫檔案”部分。由此可見,利用ChainedTransformer類能夠執行指定的程式碼。

利用TransformedMap類執行程式碼

以下為透過org.apache.commons.collections.map.TransformedMap類執行任意程式碼的示例,當執行最後的“localEntry.setValue(null);”後,會執行傳入的Transformer陣列指定的程式碼。在該示例中,會啟動計算器程式。

涉及的變數及型別

上述示例程式碼中涉及的變數及型別如下。

變數 型別
outerMap org.apache.commons.collections.map.TransformedMap
set org.apache.commons.collections.map.AbstractInputCheckedMapDecorator$EntrySet
localIterator org.apache.commons.collections.map.AbstractInputCheckedMapDecorator$EntrySetIterator
localEntry org.apache.commons.collections.map.AbstractInputCheckedMapDecorator$MapEntry

org.apache.commons.collections.map.TransformedMap類直接繼承自org.apache.commons.collections.map.AbstractInputCheckedMapDecorator類,間接繼承自java.util.Map類。org.apache.commons.collections.map.TransformedMap類的繼承關係如下。

org.apache.commons.collections.map.TransformedMap  
 └org.apache.commons.collections.map.AbstractInputCheckedMapDecorator  
  └org.apache.commons.collections.map.AbstractMapDecorator  
   └java.util.Map

呼叫TransformedMap類的decorate方法

上述示例中第33行程式碼TransformedMap.decorate呼叫了TransformedMap類的decorate方法。TransformedMap類的decorate方法中建立了TransformedMap物件,以呼叫decorate方法的引數一map作為引數呼叫了父類AbstractInputCheckedMapDecorator的建構函式,並將呼叫decorate方法的引數三valueTransformer儲存為this.valueTransformer變數。TransformedMap類相關程式碼如下。

#!java
public class TransformedMap extends AbstractInputCheckedMapDecorator implements     Serializable {

    protected final Transformer keyTransformer;
    protected final Transformer valueTransformer;

    public static Map decorate(Map map, Transformer keyTransformer,
            Transformer valueTransformer) {
        return new TransformedMap(map, keyTransformer, valueTransformer);
    }

    protected TransformedMap(Map map, Transformer keyTransformer,
            Transformer valueTransformer) {
        super(map);
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }
    ...
}

在TransformedMap類的父類AbstractInputCheckedMapDecorator的建構函式中,以自身類建構函式的引數為引數呼叫了父類的建構函式。AbstractInputCheckedMapDecorator類相關程式碼如下。

#!java
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {
    protected AbstractInputCheckedMapDecorator(Map map) {
        super(map);
    }
    ...
}

在AbstractInputCheckedMapDecorator類的父類AbstractMapDecorator的建構函式中,將建構函式的引數儲存為this.map物件。AbstractMapDecorator類相關程式碼如下。

#!java
public abstract class AbstractMapDecorator implements Map {
    protected transient Map map;

    public AbstractMapDecorator(Map map) {
        if (map == null) {
            throw new IllegalArgumentException("Map must not be null");
        }
        this.map = map;
    }
    ...
}

可以看出,上述示例程式碼中,第33行程式碼呼叫TransformedMap類的decorate方法時,引數一innerMap被儲存為生成的TransformedMap物件的map變數,引數三chain被儲存為valueTransformer變數。

呼叫AbstractInputCheckedMapDecorator類的entrySet方法

上述示例中第35行程式碼outerMap.entrySet呼叫了TransformedMap類的父類AbstractInputCheckedMapDecorator的entrySet方法。AbstractInputCheckedMapDecorator類為抽象類,在其entrySet方法中,建立了EntrySet類的物件並返回。在呼叫EntrySet類的建構函式時,引數二為this,由於AbstractInputCheckedMapDecorator類為抽象類。在上述示例程式碼執行時,引數二this即為TransformedMap類的物件outerMap。

EntrySet類為AbstractInputCheckedMapDecorator類的內部類,在其建構函式中,會將引數二儲存為this.parent變數。在上述示例程式碼執行時,TransformedMap類的物件outerMap會被儲存為EntrySet類的this.parent變數。

AbstractInputCheckedMapDecorator類相關程式碼如下。

#!java
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {
    protected boolean isSetValueChecking() {
        return true;
    }

    public Set entrySet() {
        if (isSetValueChecking()) {
            return new EntrySet(this.map.entrySet(), this);
        }
        return this.map.entrySet();
    }
    ...

    static class EntrySet extends AbstractSetDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
            super(set);
            this.parent = parent;
        }
        ...
    }
}

呼叫AbstractInputCheckedMapDecorator$EntrySet類的iterator方法

上述示例程式碼中第37行程式碼set.iterator呼叫了AbstractInputCheckedMapDecorator$EntrySet類的iterator方法。在EntrySet類的iterator方法中,建立了AbstractInputCheckedMapDecorator$EntrySetIterator類的物件並返回,在呼叫EntrySetIterator類的建構函式時,引數二為this.parent。在上述示例程式碼中,this.parent即為TransformedMap類的物件outerMap。

EntrySetIterator類為AbstractInputCheckedMapDecorator類的內部類,在其建構函式中,會將引數二儲存為this.parent變數。在上述示例程式碼執行時,TransformedMap類的物件outerMap會被儲存為EntrySetIterator類的this.parent變數。

AbstractInputCheckedMapDecorator類相關程式碼如下。

#!java
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {  
    static class EntrySet extends AbstractSetDecorator {
        private final AbstractInputCheckedMapDecorator parent;  

        public Iterator iterator() {
            return new AbstractInputCheckedMapDecorator.EntrySetIterator(
                    this.collection.iterator(), this.parent);
        }
        ...
    }

    static class EntrySetIterator extends AbstractIteratorDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected EntrySetIterator(Iterator iterator,
                AbstractInputCheckedMapDecorator parent) {
            super(iterator);
            this.parent = parent;
        }
        ...
    }
    ...
}

呼叫AbstractInputCheckedMapDecorator$EntrySetIterator類的next方法

上述示例程式碼中第39行程式碼localIterator.next呼叫了AbstractInputCheckedMapDecorator$EntrySetIterator類的next方法。在EntrySetIterator類的next方法中,建立了AbstractInputCheckedMapDecorator$MapEntry類的物件並返回,在呼叫MapEntry類的建構函式時,引數二為this.parent。在上述示例程式碼中,this.parent即為TransformedMap類的物件outerMap。

MapEntry類為AbstractInputCheckedMapDecorator類的內部類,在其建構函式中,會將引數二儲存為this.parent變數。在上述示例程式碼執行時,TransformedMap類的物件outerMap會被儲存為MapEntry類的this.parent變數。

AbstractInputCheckedMapDecorator類相關程式碼如下。

#!java
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {  

    static class EntrySetIterator extends AbstractIteratorDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        public Object next() {
            Map.Entry entry = (Map.Entry) this.iterator.next();
            return new AbstractInputCheckedMapDecorator.MapEntry(entry,
                    this.parent);
        }
        ...
    }
    ...
    static class MapEntry extends AbstractMapEntryDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Map.Entry entry,
                AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }
        ...
    }
}

呼叫AbstractInputCheckedMapDecorator$MapEntry類的setValue方法

上述示例程式碼中第43行程式碼localEntry.setValue呼叫了AbstractInputCheckedMapDecorator$MapEntry類的setValue方法。在MapEntry類的setValue方法中,呼叫了this.parent的checkSetValue方法。在上述示例程式碼中,MapEntry類的this.parent即為TransformedMap類的物件outerMap,因此會呼叫TransformedMap類的checkSetValue方法。AbstractInputCheckedMapDecorator類相關程式碼如下。

#!java
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {  
    static class MapEntry extends AbstractMapEntryDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        public Object setValue(Object value) {
            value = this.parent.checkSetValue(value);
            return this.entry.setValue(value);
        }
        ...
    }
}

在TransformedMap類的checkSetValue方法中,會呼叫this.valueTransformer.transform方法。在前文的示例程式碼中,TransformedMap類的物件outerMap的this.valueTransformer變數對應ChainedTransformer類物件chain。前文“利用ChainedTransformer執行程式碼分析”部分已經說明,呼叫ChainedTransformer類的transform方法時,會執行其在構造時傳入的ConstantTransformer與InvokerTransformer陣列中指定的方法。TransformedMap類相關程式碼如下。

#!java
public class TransformedMap extends AbstractInputCheckedMapDecorator implements
    Serializable {
    protected Object checkSetValue(Object value) {
        return this.valueTransformer.transform(value);
    }
    ...
}

綜上所述,上述示例程式碼最後的“localEntry.setValue(null);”時,會執行ConstantTransformer與InvokerTransformer陣列指定的方法。

漏洞觸發時的呼叫過程

上述漏洞在觸發時的完整呼叫過程如下。

//呼叫TransformedMap類的decorate方法  
TransformedMap.decorate  
AbstractMapDecorator.AbstractMapDecorator  
AbstractInputCheckedMapDecorator.AbstractInputCheckedMapDecorator  
TransformedMap.TransformedMap  

//呼叫AbstractInputCheckedMapDecorator類的entrySet方法  
AbstractInputCheckedMapDecorator.entrySet  
TransformedMap.isSetValueChecking  
AbstractInputCheckedMapDecorator$EntrySet.EntrySet  

//呼叫AbstractInputCheckedMapDecorator$EntrySet類的iterator方法  
AbstractInputCheckedMapDecorator$EntrySet.iterator  
AbstractInputCheckedMapDecorator$EntrySetIterator.EntrySetIterator  

//呼叫AbstractInputCheckedMapDecorator$EntrySetIterator類的next方法  
AbstractInputCheckedMapDecorator$EntrySetIterator.next  
AbstractMapEntryDecorator.AbstractMapEntryDecorator  
AbstractInputCheckedMapDecorator$MapEntry.MapEntry  

//呼叫AbstractInputCheckedMapDecorator$MapEntry類的setValue方法  
AbstractInputCheckedMapDecorator$MapEntry.setValue  
TransformedMap.checkSetValue  
ChainedTransformer.transform  
InvokerTransformer.transform

AbstractInputCheckedMapDecorator$MapEntry物件的鍵值對

在確定了利用TransformedMap類可以執行程式碼以後,再來關注上述示例程式碼中呼叫最後的“localEntry.setValue”之前的localEntry的鍵值對。之所以需要關注localEntry的鍵值對,是因為在透過AnnotationInvocationHandler類執行程式碼時,這是一個重要的變數。

從上述示例程式碼第35行“outerMap.entrySet”開始分析,之前的步驟不再重複。

上述示例中第35行程式碼outerMap.entrySet呼叫了TransformedMap類的父類AbstractInputCheckedMapDecorator的entrySet方法。在AbstractInputCheckedMapDecorator類的entrySet方法中,建立了EntrySet類的物件並返回。在呼叫EntrySet類的建構函式時,引數一為this.map.entrySet()。在上述示例程式碼中,AbstractInputCheckedMapDecorator類的this.map.entrySet()對應Map物件innerMap的entrySet()。

在AbstractInputCheckedMapDecorator$EntrySet類的建構函式中,會將引數一set作為引數呼叫父類org.apache.commons.collections.set.AbstractSetDecorator的建構函式。

AbstractInputCheckedMapDecorator類相關程式碼如下。

#!java
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {
    protected boolean isSetValueChecking() {
        return true;
    }

    public Set entrySet() {
        if (isSetValueChecking()) {
            return new EntrySet(this.map.entrySet(), this);
        }
        return this.map.entrySet();
    }
    ...

    static class EntrySet extends AbstractSetDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected EntrySet(Set set, AbstractInputCheckedMapDecorator parent) {
            super(set);
            this.parent = parent;
        }
        ...
    }
}

在AbstractSetDecorator類的建構函式中,會將引數一set作為引數呼叫父類org.apache.commons.collections.collection.AbstractCollectionDecorator的建構函式。

AbstractSetDecorator類相關程式碼如下。

#!java
public abstract class AbstractSetDecorator extends AbstractCollectionDecorator
        implements Set {
    protected AbstractSetDecorator(Set set) {
        super(set);
    }
    ...
}

在AbstractCollectionDecorator類的建構函式中,會將引數一coll儲存為this.collection變數,即AbstractCollectionDecorator類的this.collection變數儲存了示例程式碼中Map物件innerMap的entrySet()。

AbstractCollectionDecorator類相關程式碼如下。

#!java
public abstract class AbstractCollectionDecorator implements Collection {
    protected Collection collection;

    protected AbstractCollectionDecorator(Collection coll) {
        if (coll == null) {
            throw new IllegalArgumentException("Collection must not be null");
        }
        this.collection = coll;
    }
    ...
}

上述示例程式碼中第37行程式碼set.iterator呼叫了AbstractInputCheckedMapDecorator$EntrySet類的iterator方法。在EntrySet類的iterator方法中,建立了AbstractInputCheckedMapDecorator$EntrySetIterator類的物件並返回,在呼叫EntrySetIterator類的建構函式時,引數一為this.collection.iterator()。在上述示例程式碼中,this.collection.iterator()即為Map物件innerMap的entrySet().iterator()。

在AbstractInputCheckedMapDecorator$EntrySetIterator類的建構函式中,會將引數一iterator作為引數呼叫父類org.apache.commons.collections.iterators.AbstractIteratorDecorator的建構函式。

AbstractInputCheckedMapDecorator類相關程式碼如下。

#!java
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {  
    static class EntrySet extends AbstractSetDecorator {
        private final AbstractInputCheckedMapDecorator parent;  

        public Iterator iterator() {
            return new AbstractInputCheckedMapDecorator.EntrySetIterator(
                    this.collection.iterator(), this.parent);
        }
        ...
    }

    static class EntrySetIterator extends AbstractIteratorDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected EntrySetIterator(Iterator iterator,
                AbstractInputCheckedMapDecorator parent) {
            super(iterator);
            this.parent = parent;
        }
        ...
    }
    ...
}

在AbstractIteratorDecorator類的建構函式中,會將引數一iterator儲存為this.iterator變數,即AbstractIteratorDecorator類的this.iterator變數儲存了示例程式碼中Map物件innerMap的entrySet().iterator()。

AbstractIteratorDecorator類相關程式碼如下。

#!java
public class AbstractIteratorDecorator implements Iterator {
    protected final Iterator iterator;

    public AbstractIteratorDecorator(Iterator iterator) {
        if (iterator == null) {
            throw new IllegalArgumentException("Iterator must not be null");
        }
        this.iterator = iterator;
    }
    ...
}

上述示例程式碼中第39行程式碼localIterator.next呼叫了AbstractInputCheckedMapDecorator$EntrySetIterator類的next方法。在EntrySetIterator類的next方法中,建立了AbstractInputCheckedMapDecorator$MapEntry類的物件並返回,在呼叫MapEntry類的建構函式時,引數一為this.iterator.next()。在上述示例程式碼中,this.iterator.next()即為Map物件innerMap的entrySet().iterator().next(),即示例程式碼中第30行透過innerMap.put新增的鍵值對。

在AbstractInputCheckedMapDecorator$EntrySet類的建構函式中,會將引數一entry作為引數呼叫父類org.apache.commons.collections.keyvalue.AbstractMapEntryDecorator的建構函式。

AbstractInputCheckedMapDecorator類相關程式碼如下。

#!java
abstract class AbstractInputCheckedMapDecorator extends AbstractMapDecorator {  

    static class EntrySetIterator extends AbstractIteratorDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        public Object next() {
            Map.Entry entry = (Map.Entry) this.iterator.next();
            return new AbstractInputCheckedMapDecorator.MapEntry(entry,
                    this.parent);
        }
        ...
    }
    ...
    static class MapEntry extends AbstractMapEntryDecorator {
        private final AbstractInputCheckedMapDecorator parent;

        protected MapEntry(Map.Entry entry,
                AbstractInputCheckedMapDecorator parent) {
            super(entry);
            this.parent = parent;
        }
        ...
    }
}

在AbstractMapEntryDecorator類的建構函式中,會將引數一entry儲存為this.entry變數,在getKey與getValue方法會分別返回this.entry.getKey()與this.entry.getValue()。

AbstractMapEntryDecorator類相關程式碼如下。

#!java
public abstract class AbstractMapEntryDecorator implements Map.Entry, KeyValue {
    protected final Map.Entry entry;

    public AbstractMapEntryDecorator(Map.Entry entry) {
        if (entry == null) {
            throw new IllegalArgumentException("Map Entry must not be null");
        }
        this.entry = entry;
    }

    public Object getKey() {
        return this.entry.getKey();
    }

    public Object getValue() {
        return this.entry.getValue();
    }
    ...
}

綜上所述,在示例程式碼中執行第39行localIterator.next後,執行localEntry.getKey()與localEntry.getValue()可獲取示例程式碼中第30行透過innerMap.put新增的鍵值對。

利用TransformedMap與AnnotationInvocationHandler類執行程式碼

已知TransformedMap類為Map類的子類,為了觸發JAVA反序列化漏洞,需要找到某個類提供了方法接收Map物件,且在readObject方法中會呼叫Map物件的Entry的setValue方法。

sun.reflect.annotation.AnnotationInvocationHandler類滿足上述的要求。sun.reflect.annotation.AnnotationInvocationHandler類為JRE中原生的類,不需要第三方支援。

以下為透過TransformedMap與AnnotationInvocationHandler類執行任意程式碼的示例,當執行第57行的“ois.readObject();”後,會執行傳入的Transformer陣列指定的程式碼。在該示例中,會啟動計算器程式。

sun.reflect.annotation.AnnotationInvocationHandler類無法直接訪問,因此在構造需要序列化的物件時,需要使用JAVA反射機制。

在上述觸發漏洞的示例程式碼中,會呼叫AnnotationInvocationHandler類的帶引數建構函式與反序列化時會被呼叫的readObject函式。

AnnotationInvocationHandler類的重要的變數及方法如下。

#!java
1.  class AnnotationInvocationHandler implements InvocationHandler, Serializable {
2.      private final Class type;
3.      private final Map<String, ObjectmemberValues;
4.      ...
5.
6.      AnnotationInvocationHandler(Class paramClass, Map<String, ObjectparamMap) {
7.          this.type = paramClass;
8.          this.memberValues = paramMap;
9.      }
10.     ...
11.
12.     private void readObject(ObjectInputStream paramObjectInputStream)
13.             throws IOException, ClassNotFoundException {
14.         paramObjectInputStream.defaultReadObject();
15.         AnnotationType localAnnotationType = null;
16.         try {
17.             localAnnotationType = AnnotationType.getInstance(this.type);
18.         } catch (IllegalArgumentException localIllegalArgumentException) {
19.             return;
20.         }
21.         Map localMap = localAnnotationType.memberTypes();
22.         Iterator localIterator = this.memberValues.entrySet().iterator();
23.         while (localIterator.hasNext()) {
24.             Map.Entry localEntry = (Map.Entry) localIterator.next();
25.             String str = (String) localEntry.getKey();
26.             Class localClass = (Class) localMap.get(str);
27.             if (localClass != null) {
28.                 Object localObject = localEntry.getValue();
29.                 if ((!(localClass.isInstance(localObject)))
30.                         && (!(localObject instanceof ExceptionProxy)))
31.                     localEntry.setValue(new AnnotationTypeMismatchExceptionProxy(
32.                                     localObject.getClass() + "[" + localObject
33.                                             + "]")
34.                                     .setMember((Method) localAnnotationType
35.                                             .members().get(str)));
36.             }
37.         }
38.     }

示例程式碼中第43行執行newInstance方法時,對應AnnotationInvocationHandler類程式碼的第6行的帶引數構造方法。示例程式碼中第43行執行newInstance方法構造AnnotationInvocationHandler物件時,引數一為java.lang.annotation.Retention.class,引數二為TransformedMap類的物件outerMap。因此AnnotationInvocationHandler類程式碼中建構函式中儲存的this.type對應java.lang.annotation.Retention.class,this.memberValues對應示例程式碼中的outerMap。

當AnnotationInvocationHandler類的readObject方法執行時,過程如下。

  • 第17行程式碼中的this.type為java.lang.annotation.Retention.class。

  • 第21行程式碼的localMap變數存在一個鍵值對,key為字串"value",value為class"java.lang.annotation.RetentionPolicy"。

  • 第22行程式碼的this.memberValues對應示例程式碼中TransformedMap類的物件outerMap

  • 第24行程式碼的localEntry等價於outerMap.entrySet().iterator().next(),根據前文“AbstractInputCheckedMapDecorator$MapEntry物件的鍵值對”部分的分析結果,localEntry對應示例程式碼中Map物件innerMap的entrySet().iterator().next(),即示例程式碼中第34行透過innerMap.put新增的鍵值對。

  • 第25行程式碼的str等於示例程式碼中第34行透過innerMap.put新增的鍵值對的key,即字串"value"。

  • 第26行程式碼的localClass等於localMap變數中的鍵值對的value,即class"java.lang.annotation.RetentionPolicy"。

  • 第27行程式碼的判斷,需要localClass非空,滿足該條件。

  • 第28行程式碼的localObject等於示例程式碼中第34行透過innerMap.put新增的鍵值對的value,即字串"tttest"。

  • 第29行程式碼的判斷,需要localObject不是localClass的例項,localObject為String物件,localClass為class"java.lang.annotation.RetentionPolicy",滿足該條件。

  • 第30行程式碼的判斷,需要localObject不是sun.reflect.annotation.ExceptionProxy的例項,localObject為String物件,滿足該條件。

  • 第31行程式碼呼叫了localEntry變數的setValue方法,localEntry為AbstractInputCheckedMapDecorator$MapEntry類的例項,根據前文”呼叫AbstractInputCheckedMapDecorator$MapEntry類的setValue方法“部分的分析,在呼叫AbstractInputCheckedMapDecorator$MapEntry類的setValue方法時,會執行ConstantTransformer與InvokerTransformer陣列指定的方法,此時漏洞觸發。

綜上所述,在利用TransformedMap與AnnotationInvocationHandler類觸發JAVA反序列化漏洞時,有以下幾點應滿足條件。

  • 呼叫AnnotationInvocationHandler類的建構函式時,引數一應為java.lang.annotation.Retention.class;
  • 在對TransformedMap.decorate的引數一Map物件使用put設定鍵值對時,key應為字串"value";value不能為空,否則會出現空指標異常。value可設為非java.lang.annotation.RetentionPolicy或sun.reflect.annotation.ExceptionProxy類的物件,如String,Integer物件的任意值等;

利用TransformedMap與AnnotationInvocationHandler類觸發JAVA反序列化漏洞

綜合前文的分析,利用TransformedMap與AnnotationInvocationHandler類觸發JAVA反序列化漏洞的大致步驟如下。

  • 透過ConstantTransformer與InvokerTransformer陣列指定需要執行的程式碼;
  • 將ConstantTransformer與InvokerTransformer陣列轉換為ChainedTransformer物件;
  • 透過TransformedMap類的decorate方法建立陣列,引數中需要設定上一步產生的ChainedTransformer物件;
  • 使用JAVA反射機制建立AnnotationInvocationHandler類的物件,在建構函式中指定上一步建立的陣列;
  • 對AnnotationInvocationHandler物件進行序列化後,將序列化的資料傳送給JAVA中介軟體;
  • JAVA中介軟體在對序列化的AnnotationInvocationHandler類的物件資料進行反序列化時,會呼叫其readObject方法並觸發漏洞,執行ConstantTransformer與InvokerTransformer陣列指定需要執行的程式碼。

簡而言之,當攻擊者將構造好的包含攻擊程式碼序列化資料傳送給使用了Apache Commons Collections元件的JAVA中介軟體時,JAVA中介軟體在對其進行反序列化操作時,會觸發反序列化漏洞,執行攻擊者指定的任意程式碼。

不同JAVA中介軟體的JAVA反序列化漏洞利用與防護分析,之後再繼續。

參考資料

What Do WebLogic, WebSphere, JBoss, Jenkins, OpenNMS, and Your Application Have in Common This Vulnerability http://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
common-collections中Java反序列化漏洞導致的RCE原理分析 WooYun知識庫 /papers/?id=10467
Commons Collections Java反序列化漏洞深入分析 - 部落格 - 騰訊安全應急響應中心 http://security.tencent.com/index.php/blog/msg/97
JAVA Apache-CommonsCollections 序列化漏洞分析以及漏洞高階利用 隨風'S Blog http://www.iswin.org/2015/11/13/Apache-CommonsCollections-Deserialized-Vulnerability/
Java反序列化漏洞技術分析 天融信阿爾法實驗室 http://blog.topsec.com.cn/ad_lab/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E6%8A%80%E6%9C%AF%E5%88%86%E6%9E%90/
Java反序列化漏洞之Weblogic、Jboss利用教程及exp - HereSecurity http://www.heresec.com/index.php/archives/127/
Java反序列化漏洞之weblogic本地利用實現篇 - FreeBuf_COM 關注駭客與極客 http://www.freebuf.com/vuls/90802.html
Lib之過?Java反序列化漏洞通用利用分析 - Cnlouds的個人空間 - 開源中國社群 http://my.oschina.net/u/1188877/blog/529611
WebLogic之Java反序列化漏洞利用實現二進位制檔案上傳和命令執行 WooYun知識庫 /papers/?id=11690
本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章