JDK原生反序列化利用鏈7u21
- 2021 年 9 月 27 日
- 筆記
前言
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 中增加了對 傳入對象可執行方法對判斷,不能像過去一樣執行任意方法:
公眾號
歡迎大家關注我的公眾號,這裡有乾貨滿滿的硬核安全知識,和我一起學起來吧!