JAVA反序列化漏洞完整過程分析與調試

1. 前言

2016年發在烏雲知識庫,重新發出來紀念一下。

關於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。發現漏洞不是我等小白能力所及,因此本文不以挖掘漏洞的角度來進行分析,而是在已知漏洞存在的情況下分析漏洞。

2. 基礎知識

2.1. JAVA序列化與反序列化

2.1.1. JAVA序列化簡介

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

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

ObjectOutputStream 將 Java 對象的基本數據類型和圖形寫入 OutputStream。可以使用 ObjectInputStream 讀取(重構)對象。通過在流中使用文件可以實現對象的持久存儲。如果流是網絡套接字流,則可以在另一臺主機上或另一個進程中重構對象。

只能將支持 java.io.Serializable 接口的對象寫入流中。每個 serializable 對象的類都被編碼,編碼內容包括類名和類簽名、對象的字段值和數組值,以及從初始對象中引用的其他所有對象的閉包。

writeObject 方法用於將對象寫入流中。所有對象(包括 String 和數組)都可以通過 writeObject 寫入。可將多個對象或基元寫入流中。必須使用與寫入對象時相同的類型和順序從相應 ObjectInputstream 中讀回對象。

即使用ObjectOutputStream.writeObject方法可對實現了Serializable接口的對象進行序列化,序列化後的數據可存儲在文件中,或通過網絡傳輸。

2.1.2. JAVA反序列化簡介

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

ObjectInputStream 對以前使用 ObjectOutputStream 寫入的基本數據和對象進行反序列化。

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

ObjectInputStream 確保從流創建的圖形中所有對象的類型與 Java 虛擬機中顯示的類相匹配。使用標準機制按需加載類。

只有支持 java.io.Serializable 或 java.io.Externalizable 接口的對象才能從流讀取。

readObject 方法用於從流讀取對象。應該使用 Java 的安全強制轉換來獲取所需的類型。在 Java 中,字符串和數組都是對象,所以在序列化期間將其視爲對象。讀取時,需要將其強制轉換爲期望的類型。

即使用ObjectInputStream.readObject方法可對序列化的數據進行反序列化。當實現了Serializable接口的對象被反序列化時,該對象的readObject方法會被調用。

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

String實現了Serializable接口,可進行序列化。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-opbmqzvy-1583209845349)(https://cdn.jsdelivr.net/gh/Adrninistrator/tmp_upload@v0/1.JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%AE%8C%E6%95%B4%E8%BF%87%E7%A8%8B%E5%88%86%E6%9E%90%E4%B8%8E%E8%B0%83%E8%AF%95/pic/String.png)]

以下測試代碼會對String類的對象進行序列化,將序列化的數據保存在文件中,再從文件讀取序列化的數據進行反序列化。執行上述代碼後,能夠正確輸出原String類的對象的值。

2.1.4. 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的源碼中,上述變量的代碼如下。

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”,與上述描述相符。

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

以下測試代碼爲test.SerializeMyClass類,在其中定義了內部類MyObject。MyObject類實現了Serializable接口,SerializeMyClass類會對MyObject類的對象進行序列化,將序列化的數據保存在文件中,再從文件讀取序列化的數據進行反序列化。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-nN1ZhUp8-1583209845354)(https://cdn.jsdelivr.net/gh/Adrninistrator/tmp_upload@v0/1.JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%AE%8C%E6%95%B4%E8%BF%87%E7%A8%8B%E5%88%86%E6%9E%90%E4%B8%8E%E8%B0%83%E8%AF%95/pic/SerializeMyClass.png)]

執行結果如下

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

可以看到MyObject類實現的Serializable接口的readObject方法會被調用,且對象被序列化再反序列化後,對其值不影響。

生成的保存了序列化數據的文件,文件內容開頭也爲“AC ED 00 05”,可以看到文件內容包含了包名與類名、類中包含的變量名稱、類型及變量的值。

2.2. JAVA反射機制

2.2.1. 使用JAVA反射機制調用FileOutputStream類寫文件

調用FileOutputStream類寫文件時,常用的代碼如下:

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

若需要使用JAVA反射機制調用FileOutputStream類寫文件,且只允許調用Class.getMethod與Method.invoke方法,上述代碼需修改爲如下形式。

2.2.2. 使用JAVA反射機制調用Runtime類執行程序

調用Runtime類執行程序時,常用的代碼如下:

Runtime runtime = Runtime.getRuntime();
runtime.exec("calc");

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

2.2.3. JAVA反射機制與序列化

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

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

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

-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

3. 漏洞分析

3.1. 使用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.

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

JAVA中間件通常通過網絡接收客戶端發送的序列化數據,JAVA中間件在對序列化數據進行反序列化數據時,會調用被序列化對象的readObject方法。如果某個對象的readObject方法中能夠執行任意代碼,那麼JAVA中間件在對其進行反序列化時,也會執行對應的代碼。如果能夠找到滿足上述條件的對象進行序列化併發送給JAVA中間件,JAVA中間件也會執行指定的代碼,即存在反序列化漏洞。

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

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

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

3.3. 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中涉及的代碼進行反編譯,增加輸出或進行調試,可以跟蹤漏洞觸發時的代碼執行情況。

3.4. 利用ChainedTransformer執行代碼

3.4.1. ConstantTransformer類的transform方法

org.apache.commons.collections.functors.ConstantTransformer類的transform方法會返回構造函數傳入的參數。ConstantTransformer類相關代碼如下。

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;
	}
	...
}

3.4.2. InvokerTransformer類的transform方法

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

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);
		}
		...
	}
}

3.4.3. 利用ChainedTransformer執行代碼分析

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

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-FbHb6mPm-1583209845360)(https://cdn.jsdelivr.net/gh/Adrninistrator/tmp_upload@v0/1.JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%AE%8C%E6%95%B4%E8%BF%87%E7%A8%8B%E5%88%86%E6%9E%90%E4%B8%8E%E8%B0%83%E8%AF%95/pic/ExecuteChainedTransformer.png)]

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類的相關代碼如下。

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類能夠執行指定的代碼。

3.5. 利用TransformedMap類執行代碼

以下爲通過org.apache.commons.collections.map.TransformedMap類執行任意代碼的示例,當執行最後的“localEntry.setValue(null);”後,會執行傳入的Transformer數組指定的代碼。在該示例中,會啓動計算器程序。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-YkwOrujE-1583209845362)(https://cdn.jsdelivr.net/gh/Adrninistrator/tmp_upload@v0/1.JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%AE%8C%E6%95%B4%E8%BF%87%E7%A8%8B%E5%88%86%E6%9E%90%E4%B8%8E%E8%B0%83%E8%AF%95/pic/ExecuteTransformedMap.png)]

3.5.1. 涉及的變量及類型

上述示例代碼中涉及的變量及類型如下。

變量 類型
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

3.5.2. 調用TransformedMap類的decorate方法

上述示例中第33行代碼TransformedMap.decorate調用了TransformedMap類的decorate方法。TransformedMap類的decorate方法中創建了TransformedMap對象,以調用decorate方法的參數一map作爲參數調用了父類AbstractInputCheckedMapDecorator的構造函數,並將調用decorate方法的參數三valueTransformer保存爲this.valueTransformer變量。TransformedMap類相關代碼如下。

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類相關代碼如下。

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

在AbstractInputCheckedMapDecorator類的父類AbstractMapDecorator的構造函數中,將構造函數的參數保存爲this.map對象。AbstractMapDecorator類相關代碼如下。

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變量。

3.5.3. 調用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類相關代碼如下。

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;
		}
		...
	}
}

3.5.4. 調用AbstractInputCheckedMapDecorator$EntrySet類的iterator方法

上述示例代碼中第37行代碼set.iterator調用了AbstractInputCheckedMapDecoratorEntrySetiteratorEntrySetiteratorAbstractInputCheckedMapDecoratorEntrySet類的iterator方法。在EntrySet類的iterator方法中,創建了AbstractInputCheckedMapDecoratorEntrySetIterator類的對象並返回,在調用EntrySetIterator類的構造函數時,參數二爲this.parent。在上述示例代碼中,this.parent即爲TransformedMap類的對象outerMap。

EntrySetIterator類爲AbstractInputCheckedMapDecorator類的內部類,在其構造函數中,會將參數二保存爲this.parent變量。在上述示例代碼執行時,TransformedMap類的對象outerMap會被保存爲EntrySetIterator類的this.parent變量。

AbstractInputCheckedMapDecorator類相關代碼如下。

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;
		}
		...
	}
	...
}

3.5.5. 調用AbstractInputCheckedMapDecorator$EntrySetIterator類的next方法

上述示例代碼中第39行代碼localIterator.next調用了AbstractInputCheckedMapDecoratorEntrySetIteratornextEntrySetIteratornextAbstractInputCheckedMapDecoratorEntrySetIterator類的next方法。在EntrySetIterator類的next方法中,創建了AbstractInputCheckedMapDecoratorMapEntry類的對象並返回,在調用MapEntry類的構造函數時,參數二爲this.parent。在上述示例代碼中,this.parent即爲TransformedMap類的對象outerMap。

MapEntry類爲AbstractInputCheckedMapDecorator類的內部類,在其構造函數中,會將參數二保存爲this.parent變量。在上述示例代碼執行時,TransformedMap類的對象outerMap會被保存爲MapEntry類的this.parent變量。

AbstractInputCheckedMapDecorator類相關代碼如下。

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;
		}
		...
	}
}

3.5.6. 調用AbstractInputCheckedMapDecorator$MapEntry類的setValue方法

上述示例代碼中第43行代碼localEntry.setValue調用了AbstractInputCheckedMapDecorator$MapEntry類的setValue方法。在MapEntry類的setValue方法中,調用了this.parent的checkSetValue方法。在上述示例代碼中,MapEntry類的this.parent即爲TransformedMap類的對象outerMap,因此會調用TransformedMap類的checkSetValue方法。AbstractInputCheckedMapDecorator類相關代碼如下。

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類相關代碼如下。

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

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

3.5.7. 漏洞觸發時的調用過程

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

//調用TransformedMap類的decorate方法
TransformedMap.decorate
AbstractMapDecorator.AbstractMapDecorator
AbstractInputCheckedMapDecorator.AbstractInputCheckedMapDecorator
TransformedMap.TransformedMap

//調用AbstractInputCheckedMapDecorator類的entrySet方法
AbstractInputCheckedMapDecorator.entrySet
TransformedMap.isSetValueChecking
AbstractInputCheckedMapDecorator$EntrySet.EntrySet

//調用AbstractInputCheckedMapDecoratorEntrySetiteratorAbstractInputCheckedMapDecoratorEntrySet類的iterator方法 AbstractInputCheckedMapDecoratorEntrySet.iterator
AbstractInputCheckedMapDecorator$EntrySetIterator.EntrySetIterator

//調用AbstractInputCheckedMapDecoratorEntrySetIteratornextAbstractInputCheckedMapDecoratorEntrySetIterator類的next方法 AbstractInputCheckedMapDecoratorEntrySetIterator.next
AbstractMapEntryDecorator.AbstractMapEntryDecorator
AbstractInputCheckedMapDecorator$MapEntry.MapEntry

//調用AbstractInputCheckedMapDecoratorMapEntrysetValueAbstractInputCheckedMapDecoratorMapEntry類的setValue方法 AbstractInputCheckedMapDecoratorMapEntry.setValue
TransformedMap.checkSetValue
ChainedTransformer.transform
InvokerTransformer.transform

3.5.8. 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類相關代碼如下。

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類相關代碼如下。

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類相關代碼如下。

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調用了AbstractInputCheckedMapDecoratorEntrySetiteratorEntrySetiteratorAbstractInputCheckedMapDecoratorEntrySet類的iterator方法。在EntrySet類的iterator方法中,創建了AbstractInputCheckedMapDecoratorEntrySetIterator類的對象並返回,在調用EntrySetIterator類的構造函數時,參數一爲this.collection.iterator()。在上述示例代碼中,this.collection.iterator()即爲Map對象innerMap的entrySet().iterator()。

在AbstractInputCheckedMapDecorator$EntrySetIterator類的構造函數中,會將參數一iterator作爲參數調用父類org.apache.commons.collections.iterators.AbstractIteratorDecorator的構造函數。

AbstractInputCheckedMapDecorator類相關代碼如下。

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類相關代碼如下。

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調用了AbstractInputCheckedMapDecoratorEntrySetIteratornextEntrySetIteratornextAbstractInputCheckedMapDecoratorEntrySetIterator類的next方法。在EntrySetIterator類的next方法中,創建了AbstractInputCheckedMapDecoratorMapEntry類的對象並返回,在調用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類相關代碼如下。
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類相關代碼如下。

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添加的鍵值對。

3.6. 利用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數組指定的代碼。在該示例中,會啓動計算器程序。

[外鏈圖片轉存失敗,源站可能有防盜鏈機制,建議將圖片保存下來直接上傳(img-g9PKXwKd-1583209845363)(https://cdn.jsdelivr.net/gh/Adrninistrator/tmp_upload@v0/1.JAVA%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E6%BC%8F%E6%B4%9E%E5%AE%8C%E6%95%B4%E8%BF%87%E7%A8%8B%E5%88%86%E6%9E%90%E4%B8%8E%E8%B0%83%E8%AF%95/pic/ExecuteAnnotationInvocationHandler.png)]

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

在上述觸發漏洞的示例代碼中,會調用AnnotationInvocationHandler類的帶參數構造函數與反序列化時會被調用的readObject函數。

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

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方法執行時,過程如下。

1.第17行代碼中的this.type爲java.lang.annotation.Retention.class。
2.第21行代碼的localMap變量存在一個鍵值對,key爲字符串"value",value爲class"java.lang.annotation.RetentionPolicy"。
3.第22行代碼的this.memberValues對應示例代碼中TransformedMap類的對象outerMap
4.第24行代碼的localEntry等價於outerMap.entrySet().iterator().next(),根據前文“AbstractInputCheckedMapDecoratorMapEntrylocalEntryMapinnerMapentrySet().iterator().next()34innerMap.put5.25str34innerMap.putkey"value"6.26localClasslocalMapvalueclass"java.lang.annotation.RetentionPolicy"7.27localClass滿8.28localObject34innerMap.putvalue"tttest"9.29localObjectlocalClasslocalObjectStringlocalClassclass"java.lang.annotation.RetentionPolicy"滿10.30localObjectsun.reflect.annotation.ExceptionProxylocalObjectString滿11.31調localEntrysetValuelocalEntryAbstractInputCheckedMapDecoratorMapEntry對象的鍵值對”部分的分析結果,localEntry對應示例代碼中Map對象innerMap的entrySet().iterator().next(),即示例代碼中第34行通過innerMap.put添加的鍵值對。 5.第25行代碼的str等於示例代碼中第34行通過innerMap.put添加的鍵值對的key,即字符串"value"。 6.第26行代碼的localClass等於localMap變量中的鍵值對的value,即class"java.lang.annotation.RetentionPolicy"。 7.第27行代碼的判斷,需要localClass非空,滿足該條件。 8.第28行代碼的localObject等於示例代碼中第34行通過innerMap.put添加的鍵值對的value,即字符串"tttest"。 9.第29行代碼的判斷,需要localObject不是localClass的實例,localObject爲String對象,localClass爲class"java.lang.annotation.RetentionPolicy",滿足該條件。 10.第30行代碼的判斷,需要localObject不是sun.reflect.annotation.ExceptionProxy的實例,localObject爲String對象,滿足該條件。 11.第31行代碼調用了localEntry變量的setValue方法,localEntry爲AbstractInputCheckedMapDecoratorMapEntry類的實例,根據前文”調用AbstractInputCheckedMapDecoratorMapEntrysetValue調AbstractInputCheckedMapDecoratorMapEntry類的setValue方法“部分的分析,在調用AbstractInputCheckedMapDecoratorMapEntry類的setValue方法時,會執行ConstantTransformer與InvokerTransformer數組指定的方法,此時漏洞觸發。

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

1.調用AnnotationInvocationHandler類的構造函數時,參數一應爲java.lang.annotation.Retention.class;
2.在對TransformedMap.decorate的參數一Map對象使用put設置鍵值對時,key應爲字符串"value";value不能爲空,否則會出現空指針異常。value可設爲非java.lang.annotation.RetentionPolicy或sun.reflect.annotation.ExceptionProxy類的對象,如String,Integer對象的任意值等。

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

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

1.通過ConstantTransformer與InvokerTransformer數組指定需要執行的代碼;
2.將ConstantTransformer與InvokerTransformer數組轉換爲ChainedTransformer對象;
3.通過TransformedMap類的decorate方法創建數組,參數中需要設置上一步產生的ChainedTransformer對象;
4.使用JAVA反射機制創建AnnotationInvocationHandler類的對象,在構造函數中指定上一步創建的數組;
5.對AnnotationInvocationHandler對象進行序列化後,將序列化的數據發送給JAVA中間件;
6.JAVA中間件在對序列化的AnnotationInvocationHandler類的對象數據進行反序列化時,會調用其readObject方法並觸發漏洞,執行ConstantTransformer與InvokerTransformer數組指定需要執行的代碼。

簡而言之,當攻擊者將構造好的包含攻擊代碼序列化數據發送給使用了Apache Commons Collections組件的JAVA中間件時,JAVA中間件在對其進行反序列化操作時,會觸發反序列化漏洞,執行攻擊者指定的任意代碼。

不同JAVA中間件的JAVA反序列化漏洞利用與防護分析,以後再繼續。

4. 參考資料

lity/
標題 鏈接
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知識庫 http://drops.wooyun.org/papers/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知識庫 http://drops.wooyun.org/papers/11690
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知識庫 http://drops.wooyun.org/papers/11690
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章