Conceptos básicos de serialización y deserialización de Java

Vulnerabilidades de serialización y deserialización de Java

Aquí, aprenderá qué son la serialización y deserialización de Java, pensará desde la perspectiva del código y luego conducirá a problemas de seguridad. Esta forma de pensar es propicia para mejorar su comprensión de las vulnerabilidades y también es propicia para la auditoría del código. La dirección está más cerca . Por supuesto, antes de estudiar este artículo, es necesario tener cierta base en programación Java, así será más fácil de aprender.

descripción general

La serialización de Java se refiere al proceso de convertir objetos Java en secuencias de bytes; la deserialización de Java se refiere al proceso de restaurar secuencias de bytes en objetos Java.

La serialización se divide en dos partes: serialización y [deserialización. La serialización es la primera parte del proceso, que divide los datos en un flujo de bytes para almacenarlos en un archivo o transmitirlos a través de una red. La deserialización consiste en abrir el flujo de bytes y reconstruir el objeto. La serialización de objetos implica no sólo convertir tipos de datos primitivos en representaciones de bytes, sino también, a veces, restaurar los datos. La restauración de datos requiere una instancia de objeto de los datos a restaurar.

Las variables miembro estáticas no se pueden serializar porque la serialización es para objetos y las variables miembro estáticas pertenecen a clases.

Las variables modificadas por transitorio no se pueden serializar

Implementación de caso de serialización y deserialización nativas.

Crearé 4 clases para lograr la serialización desde el punto de vista de la programación. Estas 4 clases son Auto, Estudiante, PC1, PC2, donde la PC1 simula el servidor 1, la PC2 simula el servidor 2 y el Estudiante es un objeto. La simulación se pasa en la PC1. ObjectOutputStream serializa el objeto Student en un archivo y luego deserializa el archivo serializado de PC1 en un objeto Student a través de ObjectInputStream en PC2.

Sin embargo, el requisito previo para la serialización y deserialización es que el objeto debe implementar la interfaz serializable; de ​​lo contrario, se informará un error y el objeto serializado debe tener métodos get y set. Aquí uso el complemento lombok, que tiene incorporado obtener y establecer métodos

Serialización desde el punto de vista de un desarrollador

Car

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Car implements Serializable {
    
    
    private String name;
    private int wheels;
}

Student

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student implements Serializable {
    
    
  private String name;
  private int age;
  /**
  * Car 类也是需要实现序列化接口的。
  */
  private Car car;//这里如果没有实现序列化接口,那么在 Student 对象序列化时将会报错
}

PC1

public class PC1 {
    
    
    public static void main(String[] args) throws IOException {
    
    
        Car car = new Car("BYD",4);
        Student yuanBoss = new Student("yuan_boss", 18,car);
        System.out.println(yuanBoss);
        serialize(yuanBoss);
    }
    public static void serialize(Student student) throws IOException {
    
    
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("ser.bin"));
        oos.writeObject(student);
    }
}

Al serializar el objeto Estudiante, si el objeto Auto en el objeto Estudiante no implementa Serializable, se informará la siguiente excepción:

Después de implementar Serializable, puede ver el resultado correcto y puede ver que el archivo ser.bin se genera en el directorio del proyecto actual:

imagen-20230810171717269

imagen-20230810171841361

PC2

public class PC2 {
    
    
    public static void main(String[] args) throws IOException, ClassNotFoundException {
    
    
        unSerialize();
    }
    public static void unSerialize() throws IOException, ClassNotFoundException {
    
    
        ObjectInput oos = new ObjectInputStream(new FileInputStream("ser.bin"));
        Object obj = oos.readObject();
        System.out.println(obj);
    }
}

Después de ejecutar el código en PC2, podemos ver que el objeto Estudiante se genera correctamente:

imagen-20230810171919144

Provocar pensamiento

¿Por qué el código anterior se restaura en un objeto después de la deserialización? La razón es que se llama al método readObject (), pero si reescribimos el método readObject () en la clase que se va a serializar, se deserializará de acuerdo con la lógica que reescribimos al deserializar.

Por ejemplo, anulo los métodos writeObject() y readObject() en la clase Student:

@AllArgsConstructor
@NoArgsConstructor
@Data
public class Student implements Serializable {
    
    
  private String name;
  private int age;
  /**
  * Car 类也是需要实现序列化接口的。
  */
  private Car car;//这里如果没有实现序列化接口,那么在 Student 对象序列化时将会报错
  private void writeObject(ObjectOutputStream objectOutputStream){
    
    
    System.out.println("重写的writeObject方法");
  }
  private void readObject(ObjectInputStream objectInputStream){
    
    
    System.out.println("重写的readObject方法");
  }
  
}

Cuando ejecutamos PC1, no se generará el archivo ser.bin, pero 重写的writeObject方法se generará esta oración. Después de ejecutar PC2, se llamará al método readObject () reescrito en lugar de deserializarlo en un objeto.

Como se muestra en la figura, ejecute PC1:

imagen-20230810174602996

Ejecute PC2:

imagen-20230810174627521

plantea preocupaciones de seguridad

A través del pensamiento anterior, podemos saber que al reescribir los métodos readObject () y writeObject (), la lógica de los métodos reescritos readObject () y writeObject () se puede ejecutar cuando el programa se serializa o deserializa, lo cual es equivalente. a tener la capacidad de ejecutar código en el servidor. Por supuesto, para tener esta capacidad, el objeto necesita cumplir las siguientes condiciones:

  1. Implementa la interfaz serializable.
  2. Anular el método readObject

En Java, HashMap cumple con esta condición. ¿Por qué HashMap reescribe el método readObject ()? Porque cuando hashmap almacena objetos, necesita calcular el valor hash para determinar la ubicación de almacenamiento del objeto. Sin embargo, los valores hash calculados por diferentes máquinas son diferentes Lo mismo, por lo tanto, para garantizar que el mismo objeto tenga el mismo valor hash en diferentes máquinas, se reescribe el método readObject (), se extrae la información del objeto durante la deserialización y luego se calcula el valor hash. Calculado para garantizar el valor hash del objeto. Mismo.

imagen-20230810175807999

Entonces podemos usar HashMap para colocar algunos objetos maliciosos en HashMap. Cuando se deserializa HashMap, llamará automáticamente al método readObject () en HashMap para completar el uso de algunos objetos maliciosos. En la cadena de explotación CC6 y la cadena URLDNS, se utiliza readObject () de HashMap y luego se activa la cadena de explotación.

Caso de uso de HashMap

Los maestros que tengan los conceptos básicos de serialización y deserialización de Java pueden intentar leer los siguientes artículos:

Análisis del código fuente de URLDNS escrito por la cadena EXP

Cadena de utilización de CC6 (la mejor cadena de utilización de CC) – Ideas de escritura EXP – análisis de código fuente

Por supuesto, sigo recomendando leer en el orden en que está organizada esta columna para obtener mejores resultados.

Supongo que te gusta

Origin blog.csdn.net/weixin_46367450/article/details/132326437
Recomendado
Clasificación