Basic Java Deserialization with ObjectInputStream readObject
Tip
Impara e pratica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord, al gruppo telegram, segui @hacktricks_live su X/Twitter, oppure controlla la pagina LinkedIn e il canale YouTube.
- Condividi hacking tricks inviando PR ai repository github HackTricks e HackTricks Cloud.
In questo POST verrà spiegato un esempio che usa java.io.Serializable e perché sovrascrivere readObject() può essere estremamente pericoloso se lo stream in ingresso è attacker-controlled.
Serializable
L’interfaccia Java Serializable (java.io.Serializable) è una marker interface che le tue classi devono implementare se devono essere serialized e deserialized. La Java object serialization (writing) viene eseguita con il ObjectOutputStream e la deserialization (reading) viene eseguita con il ObjectInputStream.
Promemoria: Quali metodi vengono invocati implicitamente durante la deserialization?
readObject()– logica di lettura specifica della classe (se implementato e private).readResolve()– può sostituire l’oggetto deserialized con un altro.validateObject()– tramite callback diObjectInputValidation.readExternal()– per le classi che implementanoExternalizable.- I constructors non vengono eseguiti – perciò le gadget chains si affidano esclusivamente ai callback precedenti.
Qualsiasi metodo in quella catena che finisca per invocare dati attacker-controlled (command execution, JNDI lookups, reflection, ecc.) trasforma la routine di deserialization in un RCE gadget.
Vediamo un esempio con una class Person che è serializable. Questa classe sovrascrive la readObject function, quindi quando qualsiasi oggetto di questa classe viene deserialized questa funzione verrà eseguita.
Nell’esempio, la funzione readObject della class Person chiama la funzione eat() del suo pet e la funzione eat() di un Dog (per qualche motivo) avvia calc.exe. Vedremo come serializzare e deserializzare un oggetto Person per eseguire questa calcolatrice:
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");
}
}
Conclusione (scenario classico)
Come si vede in questo esempio molto basilare, la “vulnerabilità” qui appare perché il metodo readObject() sta chiamando altro codice controllato dall’attaccante. Nelle reali gadget chains, migliaia di classi contenute in librerie esterne (Commons-Collections, Spring, Groovy, Rome, SnakeYAML, ecc.) possono essere abusate – all’attaccante serve un solo gadget raggiungibile per ottenere l’esecuzione di codice.
2023-2025: Cosa è cambiato nei bug reali di Java deserialization?
I casi recenti ricordano che i bug di ObjectInputStream non sono più solo “uploadare un file .ser a un endpoint HTTP legacy”:
- Broker / queue consumers: Spring-Kafka (
CVE-2023-34040) ha mostrato che deserializzare gli exception headers da topic controllati dall’attaccante è sufficiente se il consumer abilita i non usuali flagcheckDeserExWhen*. - Client-side trust of remote servers: the Aerospike Java client (
CVE-2023-36480) deserialized objects received from the server. La risposta del vendor è stata rilevante: i client più recenti hanno rimosso il supporto alla Java runtime serialization/deserialization invece di cercare di preservarlo dietro un filtro debole. - “Restricted” streams are often still too broad:
pac4j-core(CVE-2023-25581) ha tentato di proteggere la deserializzazione conRestrictedObjectInputStream, ma l’insieme di classi accettate era comunque sufficientemente ampio da rendere possibile l’abuso di gadget.
La lezione offensiva è che il confine di trust pericoloso spesso non è “l’utente carica un blob”, ma “un componente che lo sviluppatore riteneva trusted può inserire byte in uno stream che alla fine raggiunge readObject()”.
Se ti servono controlli di reachability a basso rumore prima di investire tempo in ricerca completa di gadget, usa le pagine Java dedicate per:
Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner
Anti-pattern di readObject() che creano ancora entrypoint per gadget
Anche se la tua classe non è un evidente gadget RCE, i seguenti pattern sono sufficienti per renderla sfruttabile quando oggetti controllati dall’attaccante sono incorporati nel graph:
- Chiamare metodi sovrascrivibili o metodi di interfaccia da
readObject()(pet.eat()nel PoC sopra è l’esempio classico). - Eseguire lookup, reflection, class loading, valutazione di espressioni, o operazioni JNDI durante la deserializzazione.
- Iterare su collection o map controllate dall’attaccante, il che può scatenare
hashCode(),equals(), comparators, o transformers come effetti collaterali. - Registrare callback
ObjectInputValidationche eseguono post-processing pericoloso. - Assumere che “private
readObject()” sia una protezione sufficiente. Controlla solo la semantica di dispatch; non rende la deserializzazione sicura.
Mitigazioni moderne da implementare
- JEP 290 / Serialization Filtering (Java 9+) Usa una allow-list e limiti espliciti sul graph:
-Djdk.serialFilter="com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;maxbytes=16384;!*"
- Applica un filtro su ogni stream non trusted, non 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 Preferisci questo approccio quando la stessa JVM ha molteplici contesti di deserializzazione (RMI, cache replication, message consumers, admin-only imports) e ciascuno richiede una allow-list diversa.
- Mantieni
readObject()minimale Chiama solodefaultReadObject()/ letture esplicite dei campi, quindi esegui controlli stretti degli invarianti. Non fare I/O, logging che dereference oggetti controllati dall’attaccante, lookup dinamici, o chiamate di metodo su sub-object deserializzati. - Se possibile, rimuovi la serialization nativa Java dal design
La correzione di Aerospike è un buon modello: quando la feature non è essenziale, eliminare l’uso di
readObject()/writeObject()è spesso più sicuro che cercare di mantenere filtri perfetti per sempre.
Detection and research workflow
ysoserialrimane il riferimento per la validazione dei gadget e probe veloci RCE/URLDNS.marshalsecè ancora utile quando il sink pivota verso il territorio JNDI/LDAP/RMI.GadgetInspectorè utile quando hai gli jar target e devi cercare gadget chains specifiche dell’applicazione.- Java 17 ha aggiunto l’evento Flight Recorder
jdk.Deserialization, utile per vedere doveObjectInputStreamè effettivamente usato e se vengono applicati filtri.
Quick checklist for secure readObject() implementations
- Rendi il metodo
privatee annota gli hook di serialization con@Serialin modo che i compilatori possano rilevare firme dichiarate in modo errato. - Chiama
defaultReadObject()per primo a meno che tu non abbia una forte ragione per leggere manualmente l’intero object graph. - Tratta ogni oggetto annidato come controllato dall’attaccante finché non è validato.
- Non invocare mai metodi su collaborator deserializzati dall’interno di
readObject(). - Affianca la code review con una review dell’
ObjectInputFilter; un “codicereadObject()dall’aspetto sicuro” non basta se lo stream continua ad accettare classi arbitrarie.
References
- OpenJDK JEP 415: Context-Specific Deserialization Filters
- GitHub Security Lab: GHSL-2022-085 / CVE-2023-25581 (
pac4j-coredeserialization leading to RCE)
Tip
Impara e pratica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord, al gruppo telegram, segui @hacktricks_live su X/Twitter, oppure controlla la pagina LinkedIn e il canale YouTube.
- Condividi hacking tricks inviando PR ai repository github HackTricks e HackTricks Cloud.


