先看 ysoserial 中 CommonsBeanutils1 这条链的代码
最后返回的是 PriorityQueue<Object> queue
,看一下 PriorityQueue 这个类的定义。
PriorityQueue类在Java1.5中引入并作为 Java Collections Framework 的一部分。PriorityQueue是基于优先堆的一个无界队列,这个优先队列中的元素可以默认自然排序或者通过提供的Comparator(比较器)在队列实例化的时排序。
简单来说就是 PriorityQueue 会对队列中的元素用比较器 Comparator 进行排序,而 CommonsBeanutils1 中使用的比较器为 BeanComparator。
1 | final BeanComparator comparator = new BeanComparator("lowestSetBit"); |
这里先放一放,我们看看 PriorityQueue 是如何反序列化的,
直接看 PriorityQueue#readObject() 方法的代码,前边就是将队列中的对象反序列化,最后会调用 PriorityQueue#heapify() 方法。
java.uti.PriorityQueue.java
跟进 PriorityQueue#heapify() 方法,这里应该就是排序的代码,遍历队列中的元素传入 PriorityQueue#siftDown() 方法
java.uti.PriorityQueue.java
PriorityQueue#siftDown()
java.uti.PriorityQueue.java
PriorityQueue#siftDownUsingComparator()
java.uti.PriorityQueue.java
一路跟到 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
在 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 | package payload; |
成功执行命令
前边说的 PriorityQueue 反序列化的时候会对队列中的元素使用比较器的 compare 方法,所以效果也是一样的,反序列化时的调用链:
1 | PriorityQueue.readObject() |