Deserialización Java básica con ObjectInputStream readObject
Tip
Aprende y practica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Revisa el catálogo completo de HackTricks Training para las rutas de evaluación (ARTA/GRTA/AzRTA) y Linux Hacking Expert (LHE).
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord, al grupo de telegram, sigue @hacktricks_live en X/Twitter, o revisa la página de LinkedIn y el canal de YouTube.
- Comparte hacking tricks enviando PRs a los repositorios de github HackTricks y HackTricks Cloud.
En esta entrada se explicará un ejemplo usando java.io.Serializable y por qué sobreescribir readObject() puede ser extremadamente peligroso si el stream entrante está controlado por un atacante.
Serializable
La interfaz Java Serializable (java.io.Serializable) es una interfaz marcador que tus clases deben implementar si van a ser serializadas y deserializadas. La serialización de objetos Java (escritura) se realiza con el ObjectOutputStream y la deserialización (lectura) se realiza con el ObjectInputStream.
Recordatorio: ¿Qué métodos se invocan implícitamente durante la deserialización?
readObject()– lógica de lectura específica de la clase (si está implementada y es private).readResolve()– puede reemplazar el objeto deserializado con otro.validateObject()– vía callbacks deObjectInputValidation.readExternal()– para clases que implementanExternalizable.- Los constructores no se ejecutan – por lo tanto las gadget chains dependen exclusivamente de las callbacks anteriores.
Cualquier método en esa cadena que termine invocando datos controlados por el atacante (ejecución de comandos, búsquedas JNDI, reflection, etc.) convierte la rutina de deserialización en un gadget de RCE.
Veamos un ejemplo con una clase Person que es serializable. Esta clase sobrescribe el readObject function, so when any object of this class is deserialized this function is going to be executed.
En el ejemplo, la función readObject de la clase Person llama a la función eat() de su mascota y la función eat() de un Dog (por alguna razón) ejecuta un calc.exe. Vamos a ver cómo serializar y deserializar un objeto Person para ejecutar esta calculadora:
The following example is from https://medium.com/@knownsec404team/java-deserialization-tool-gadgetinspector-first-glimpse-74e99e493649
import java.io.Serializable;
import java.io.*;
public class TestDeserialization {
interface Animal {
public void eat();
}
//Class must implements Serializable to be serializable
public static class Cat implements Animal,Serializable {
@Override
public void eat() {
System.out.println("cat eat fish");
}
}
//Class must implements Serializable to be serializable
public static class Dog implements Animal,Serializable {
@Override
public void eat() {
try {
Runtime.getRuntime().exec("calc");
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("dog eat bone");
}
}
//Class must implements Serializable to be serializable
public static class Person implements Serializable {
private Animal pet;
public Person(Animal pet){
this.pet = pet;
}
//readObject implementation, will call the readObject from ObjectInputStream and then call pet.eat()
private void readObject(java.io.ObjectInputStream stream)
throws IOException, ClassNotFoundException {
pet = (Animal) stream.readObject();
pet.eat();
}
}
public static void GeneratePayload(Object instance, String file)
throws Exception {
//Serialize the constructed payload and write it to the file
File f = new File(file);
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(f));
out.writeObject(instance);
out.flush();
out.close();
}
public static void payloadTest(String file) throws Exception {
//Read the written payload and deserialize it
ObjectInputStream in = new ObjectInputStream(new FileInputStream(file));
Object obj = in.readObject();
System.out.println(obj);
in.close();
}
public static void main(String[] args) throws Exception {
// Example to call Person with a Dog
Animal animal = new Dog();
Person person = new Person(animal);
GeneratePayload(person,"test.ser");
payloadTest("test.ser");
// Example to call Person with a Cat
//Animal animal = new Cat();
//Person person = new Person(animal);
//GeneratePayload(person,"test.ser");
//payloadTest("test.ser");
}
}
Conclusión (escenario clásico)
Como se puede ver en este ejemplo muy básico, la “vulnerabilidad” aquí aparece porque el método readObject() está llamando a otro código controlado por el atacante. En cadenas de gadgets del mundo real, miles de clases contenidas en bibliotecas externas (Commons-Collections, Spring, Groovy, Rome, SnakeYAML, etc.) pueden ser abusadas: el atacante solo necesita un gadget alcanzable para obtener ejecución de código.
2023-2025: ¿Qué cambió en los bugs de deserialización Java en el mundo real?
Los casos recientes recuerdan que los bugs de ObjectInputStream ya no son solo “subir un archivo .ser a un endpoint HTTP legado”:
- Broker / queue consumers: Spring-Kafka (
CVE-2023-34040) demostró que deserializar encabezados de excepciones desde topics controlados por el atacante es suficiente si el consumidor habilita las inusuales banderascheckDeserExWhen*. - Client-side trust of remote servers: el cliente Java de Aerospike (
CVE-2023-36480) deserializó objetos recibidos del servidor. La respuesta del proveedor fue notable: los clientes más nuevos eliminaron el soporte de serialización/deserialización del runtime de Java en lugar de intentar preservarlo detrás de un filtro débil. - “Restricted” streams are often still too broad:
pac4j-core(CVE-2023-25581) intentó proteger la deserialización conRestrictedObjectInputStream, pero el conjunto de clases aceptadas seguía siendo lo suficientemente grande como para permitir el abuso de gadgets.
La lección ofensiva es que el límite de confianza peligroso a menudo no es “el usuario sube un blob”, sino “algún componente que el desarrollador consideraba confiable puede inyectar bytes en un stream que eventualmente llega a readObject()”.
Si necesitas comprobaciones de alcanzabilidad de bajo ruido antes de dedicar tiempo a la investigación completa de gadgets, usa las páginas Java dedicadas para:
Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner
Anti-patterns de readObject() que aún crean puntos de entrada para gadgets
Aunque tu clase no sea por sí misma un gadget RCE obvio, los siguientes patrones son suficientes para hacerla explotable cuando objetos controlados por el atacante estén incrustados en el grafo:
- Llamar a métodos sobrescribibles o métodos de interfaz desde
readObject()(pet.eat()en el PoC arriba es el ejemplo clásico). - Realizar lookups, reflection, carga de clases, evaluación de expresiones u operaciones JNDI durante la deserialización.
- Iterar sobre colecciones o mapas controlados por el atacante, lo cual puede desencadenar
hashCode(),equals(), comparators o transformers como efectos secundarios. - Registrar callbacks
ObjectInputValidationque realicen post-procesamiento peligroso. - Asumir que “private
readObject()” es suficiente protección. Solo controla la semántica de despacho; no hace que la deserialización sea segura.
Mitigaciones modernas que deberías desplegar
- JEP 290 / Serialization Filtering (Java 9+) Usa una allow-list y límites explícitos del grafo:
-Djdk.serialFilter="com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;maxbytes=16384;!*"
- Aplica un filtro en cada stream no confiable, no solo globalmente:
try (var ois = new ObjectInputStream(input)) {
var filter = ObjectInputFilter.Config.createFilter(
"com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;!*"
);
ois.setObjectInputFilter(filter);
return (Message) ois.readObject();
}
- JEP 415 (Java 17+) Context-Specific Filter Factories Prefiere esto cuando la misma JVM tiene múltiples contextos de deserialización (RMI, cache replication, message consumers, admin-only imports) y cada uno necesita una allow-list diferente.
- Mantén
readObject()aburrido Solo llama adefaultReadObject()/ lecturas explícitas de campos, luego realiza comprobaciones estrictas de invariantes. No hagas I/O, logging que desreferencie objetos controlados por el atacante, lookups dinámicos, ni llamadas a métodos sobre subobjetos deserializados. - Si es posible, elimina la serialización nativa de Java del diseño
El arreglo de Aerospike es un buen modelo: cuando la característica no es esencial, eliminar el uso de
readObject()/writeObject()suele ser más seguro que intentar mantener filtros perfectos para siempre.
Flujo de trabajo de detección e investigación
ysoserialsigue siendo la referencia para la validación de gadgets y probes rápidos de RCE/URLDNS.marshalsecsigue siendo útil cuando el sink pivota hacia territorio JNDI/LDAP/RMI.GadgetInspectores útil cuando tienes los jars del objetivo y necesitas buscar cadenas de gadgets específicas de la aplicación.- Java 17 añadió el evento
jdk.Deserializationdel Flight Recorder, que es útil para ver dónde se usa realmenteObjectInputStreamy si se están aplicando filtros.
Lista de verificación rápida para implementaciones seguras de readObject()
- Haz el método
privatey anota los hooks de serialización con@Serialpara que los compiladores detecten firmas mal declaradas. - Llama a
defaultReadObject()primero a menos que tengas una razón sólida para leer manualmente todo el grafo de objetos. - Trata cada objeto anidado como controlado por el atacante hasta que esté validado.
- Nunca invoques métodos sobre colaboradores deserializados desde dentro de
readObject(). - Acompaña la revisión de código con una revisión de
ObjectInputFilter; el “códigoreadObject()que parece seguro” no es suficiente si el stream aún acepta clases arbitrarias.
References
- OpenJDK JEP 415: Context-Specific Deserialization Filters
- GitHub Security Lab: GHSL-2022-085 / CVE-2023-25581 (
pac4j-coredeserialization leading to RCE)
Tip
Aprende y practica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Aprende y practica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Revisa el catálogo completo de HackTricks Training para las rutas de evaluación (ARTA/GRTA/AzRTA) y Linux Hacking Expert (LHE).
Apoya a HackTricks
- Revisa los planes de suscripción!
- Únete al 💬 grupo de Discord, al grupo de telegram, sigue @hacktricks_live en X/Twitter, o revisa la página de LinkedIn y el canal de YouTube.
- Comparte hacking tricks enviando PRs a los repositorios de github HackTricks y HackTricks Cloud.


