前言
JDK 7u21以前只粗略的掃過一眼,一看使用了AnnotationInvocationHandler,就以為還是和 CC1 一樣差不多的利用方式,但最近仔細看了下利用鏈發現事情並不簡單~
7u21 要求你能理解:
- TemplatesImpl 程式碼執行原理
- 動態代理是什麼
- AnnotationInvocationHandler 利用原理
其實7u21是對AnnotationInvocationHandler 的進一步挖掘。
呼叫鏈
- HashSet.readObject()
- map.put(k,v)。(k為代理物件)
- k.equals(last_k) (last_k 為上一個put的元素)
- k.equalImpl(). (觸發惡意invoker)
- 反射執行 last_k.任意方法 (last_k 為惡意TemplatesImpl 即可程式碼執行)
簡單描述就是:
LinkedHashSet 在反序列化初始化時會將物件重新一個一個put進內部資料結構table中,如果put的兩個元素的key的hash一樣,則要進行進一步的判斷,要呼叫後放入元素的equal方法去對比前面元素的值,而如果這個後面元素的key恰好是一個經過AnnotationInvocationHandler包裝的Templates動態代理物件,則在進行put時候會分別呼叫 AnnotationInvocationHandler 的hashImpl和equlImpl,恰好因為hashImpl 的計算會最終呼叫到equlImpl,通過equlImpl中的var5.invoke() ,反射執行Templates 的newTransformer方法,進而導致程式碼執行。
我們來看下yso的程式碼:
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
String zeroHashCodeStr = "f5a5a608";
HashMap map = new HashMap();
map.put(zeroHashCodeStr, "foo");
InvocationHandler tempHandler = (InvocationHandler) Reflections.getFirstCtor(Gadgets.ANN_INV_HANDLER_CLASS).newInstance(Override.class, map);
Reflections.setFieldValue(tempHandler, "type", Templates.class);
Templates proxy = Gadgets.createProxy(tempHandler, Templates.class);
LinkedHashSet set = new LinkedHashSet(); // maintain order
set.add(templates);
set.add(proxy);
Reflections.setFieldValue(templates, "_auxClasses", null);
Reflections.setFieldValue(templates, "_class", null);
map.put(zeroHashCodeStr, templates); // swap in real object
return set;
}
最終的目標是要呼叫EvilTemples的newTransformer方法,在AnnotationInvocationHandler 中有個equalsImpl 方法:
private Boolean equalsImpl(Object var1) {
if (var1 == this) {
return true;
} else if (!this.type.isInstance(var1)) {
return false;
} else {
Method[] var2 = this.getMemberMethods();
int var3 = var2.length;
for(int var4 = 0; var4 < var3; ++var4) {
Method var5 = var2[var4];
String var6 = var5.getName();
Object var7 = this.memberValues.get(var6);
Object var8 = null;
AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
if (var9 != null) {
var8 = var9.memberValues.get(var6);
} else {
try {
var8 = var5.invoke(var1);
} catch (InvocationTargetException var11) {
return false;
} catch (IllegalAccessException var12) {
throw new AssertionError(var12);
}
}
if (!memberValueEquals(var7, var8)) {
return false;
}
}
return true;
}
}
拋開干擾程式碼存在一個反射執行的方法:
Method[] var2 = this.getMemberMethods();
for(int var4 = 0; var4 < var3; ++var4) {
Method var5 = var2[var4];
AnnotationInvocationHandler var9 = this.asOneOfUs(var1);
if (var9 != null)
{
....
}else{
var8 = var5.invoke(var1);
}
其中getMemberMethods
來自type的所有方法:
那意思就是equalsImpl能夠觸發一個傳入物件x的惡意方法,有沒有存在一個這樣的物件呢,當然是有的,那就是 TemplatesImpl,TemplatesImpl.newTransformer()和getOutputProperties() 都可以觸發惡意程式碼執行,其中asOneOfUs判斷是不是動態代理的class,所以只要保證var1 不是動態代理就ok。
所以我們就需要把type設定為TemplatesImpl,var1 也為TemplatesImpl物件即可,var1也即hashset的舊元素,為了能觸發惡意invoker,所以hashset的第二個元素要為一個經過AnnotationInvocationHandler包裝的物件。
也就是:
步驟很清晰,有了前面幾篇文章的基礎後沒必要再分析TemplatesImpl和AnnotationInvocationHandler利用過程了,重點看下以下幾個問題
疑問
- 上圖為Hashset put的原始碼,為了要執行euqals,就要保證map的兩個key hash一致,且兩個key不想等,key分別為templates和proxy,是兩個不同的物件,兩個hash是如何保持一致的?
- hash為0的字串的作用是啥?
- 為什麼要用LinkedHashSet,直接用HashSet行不行?
1. 兩個不相等的物件如何保證hashset.hash()後結果一致?
在7u21的反序列化執行載體hashset中,新增了兩個元素,一前一後分別是templates和proxy,雖然proxy物件是經過AnnotationInvocationHandler包裝的Templates 代理物件,但這兩個物件是不相等的,為了能執行後續步驟,讓流程執行到for迴圈下的if預計又必須保證兩個物件的hashcode一致才能進一步執行到euqals方法。
如果只是普通的兩個Temples物件那必然是不相等的,但第二個元素proxy經過AnnotationInvocationHandler包裝的Templates物件,當proxy執行hashcode時,會直接執行handler.invoker函式:
同時,判斷執行方法為hashcode則會跳轉到hashCodeImpl方法:
跳轉到hashCodeImpl 時我們發現,關於proxy的hashcode計算已經和proxy本身沒有關係了,私有變數memberValues是一個map物件,圖片中的1會進行迭代,取出其中元素key和value 分別呼叫hashcode()進行異或運算然後乘以127 得到最後prxoy的hashcode值。
所以為了讓prxoy和templates 的hashcode一致,我們只要 構造一個map,使其滿足:
127 * key.hashcode ^ value.hashcode === templates.hashcode
這裡順便解釋了:
hash為0的字串的作用是啥?
那我們可以找到,因為物件的hashcode都是取的記憶體地址做一定的取整運算得到,所以如果硬湊兩個不相等物件做異或會很複雜,結果必然不固定,所以我們直接令 key.hashcode=0
, 127*key.hashcode
也就為0,讓value 直接等於templates 即可讓 proxy的hashcode等於templates,而f5a5a608
的hashcode就為0,所以這個map為:
map.put("f5a5a608",templates)
這樣就滿足了物件不相等,但hashcode卻是一樣的結果,也就能進行到euqals方法觸發temples.newTransformer() 方法。
為什麼要用LinkedHashSet,直接用HashSet行不行?
LinkedHashSet內部的資料結構是基於hashmap,所以理論上HashSet、LinkedHashMap、hashmap都可以,但因為類似我上面講的歡迎,對於物件的hash值,是根據物件保持在記憶體中的地址取整得到的,所以就會存在一定的隨機性,不管是序列化還是反序列化都無法保證proxy做為第二個元素被新增進去,也就沒發保證euqal方法會觸發proxy的invoker,無法執行templates的可執行方法。
我自己也做過實驗,使用hashmap作為載體會存在偶然無法程式碼執行的情況,所以為了保證穩定性可以使用帶順序的LinkedHashSet、或者LinkedHashmap,LinkedHashmap value設定為相同的內建基本型別即可。
結語
由名字可知,jdk7u21 只能用於7u21以下的版本,因為在高版本中,在handler euqalmpl 中增加了對 傳入物件可執行方法對判斷,不能像過去一樣執行任意方法:
公眾號
歡迎大家關注我的公眾號,這裡有乾貨滿滿的硬核安全知識,和我一起學起來吧!