Basic Java Deserialization with ObjectInputStream readObject
Reading time: 7 minutes
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.
In questo POST verrà spiegato un esempio utilizzando java.io.Serializable
e perché sovrascrivere readObject()
può essere estremamente pericoloso se il flusso in arrivo è controllato dall'attaccante.
Serializable
L'interfaccia Java Serializable
(java.io.Serializable
) è un'interfaccia marker che le tue classi devono implementare se devono essere serializzate e deserializzate. La serializzazione degli oggetti Java (scrittura) viene eseguita con ObjectOutputStream
e la deserializzazione (lettura) viene eseguita con ObjectInputStream
.
Promemoria: Quali metodi vengono invocati implicitamente durante la deserializzazione?
readObject()
– logica di lettura specifica della classe (se implementata e privata).readResolve()
– può sostituire l'oggetto deserializzato con un altro.validateObject()
– tramite callbackObjectInputValidation
.readExternal()
– per classi che implementanoExternalizable
.- I costruttori non vengono eseguiti – quindi le catene di gadget si basano esclusivamente sui callback precedenti.
Qualsiasi metodo in quella catena che finisce per invocare dati controllati dall'attaccante (esecuzione di comandi, ricerche JNDI, riflessione, ecc.) trasforma la routine di deserializzazione in un gadget RCE.
Vediamo un esempio con una classe Person che è serializzabile. Questa classe sovrascrive la funzione readObject, quindi quando qualsiasi oggetto di questa classe viene deserializzato, questa funzione verrà eseguita.
Nell'esempio, la funzione readObject della classe Person chiama la funzione eat()
del suo animale domestico e la funzione eat()
di un Cane (per qualche motivo) chiama un calc.exe. Vedremo come serializzare e deserializzare un oggetto Person per eseguire questa calcolatrice:
Il seguente esempio è tratto da 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 puoi vedere in questo esempio molto basilare, la “vulnerabilità” qui appare perché il metodo readObject() sta chiamando altro codice controllato dall'attaccante. Nelle catene di gadget del mondo reale, migliaia di classi contenute in librerie esterne (Commons-Collections, Spring, Groovy, Rome, SnakeYAML, ecc.) possono essere abusate – l'attaccante ha bisogno solo di uno gadget raggiungibile per ottenere l'esecuzione del codice.
2023-2025: Novità negli attacchi di deserializzazione Java?
- 2023 – CVE-2023-34040: La deserializzazione degli header dei record di errore di Spring-Kafka quando i flag
checkDeserExWhen*
sono abilitati ha permesso la costruzione arbitraria di gadget da argomenti pubblicati dall'attaccante. Risolto in 3.0.10 / 2.9.11. ¹ - 2023 – CVE-2023-36480: Assunzione di server fidati del client Java Aerospike rotta – le risposte del server malevole contenevano payload serializzati che sono stati deserializzati dal client → RCE. ²
- 2023 – CVE-2023-25581: L'analisi degli attributi del profilo utente di
pac4j-core
accettava blob Base64 con prefisso{#sb64}
e li deserializzava nonostante unRestrictedObjectInputStream
. Aggiornare ≥ 4.0.0. - 2023 – CVE-2023-4528: Il servizio JSCAPE MFT Manager (porta 10880) accettava oggetti Java codificati in XML portando a RCE come root/SYSTEM.
- 2024 – Sono state aggiunte nuove catene di gadget a ysoserial-plus(mod) inclusi Hibernate5, TomcatEmbed e classi SnakeYAML 2.x che bypassano alcuni vecchi filtri.
Mitigazioni moderne che dovresti implementare
- JEP 290 / Filtro di Serializzazione (Java 9+) Aggiungi una lista di autorizzazione o di negazione delle classi:
# Accetta solo i tuoi DTO e java.base, rifiuta tutto il resto
-Djdk.serialFilter="com.example.dto.*;java.base/*;!*"
Esempio programmatico:
var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" );
ObjectInputFilter.Config.setSerialFilter(filter);
- JEP 415 (Java 17+) Fabbriche di Filtri Specifici per Contesto – utilizza un
BinaryOperator<ObjectInputFilter>
per applicare filtri diversi per contesto di esecuzione (ad es., per chiamata RMI, per consumatore di coda di messaggi). - Non esporre
ObjectInputStream
grezzo sulla rete – preferire codifiche JSON/Binary senza semantiche di esecuzione del codice (Jackson dopo aver disabilitatoDefaultTyping
, Protobuf, Avro, ecc.). - Limiti di Difesa in Profondità – Imposta la lunghezza massima dell'array, la profondità, i riferimenti:
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
- Scansione continua dei gadget – esegui strumenti come
gadget-inspector
oserialpwn-cli
nel tuo CI per far fallire la build se un gadget pericoloso diventa raggiungibile.
Scheda di riferimento degli strumenti aggiornata (2024)
ysoserial-plus.jar
– fork della comunità con > 130 catene di gadget:
java -jar ysoserial-plus.jar CommonsCollections6 'calc' | base64 -w0
marshalsec
– rimane il riferimento per la generazione di gadget JNDI (LDAP/RMI).gadget-probe
– scoperta rapida di gadget black-box contro servizi di rete.SerialSniffer
– agente JVMTI che stampa ogni classe letta daObjectInputStream
(utile per creare filtri).- Suggerimento per la rilevazione – abilita
-Djdk.serialDebug=true
(JDK 22+) per registrare le decisioni del filtro e le classi rifiutate.
Checklist rapida per implementazioni sicure di readObject()
- Rendi il metodo
private
e aggiungi l'annotazione@Serial
(aiuta l'analisi statica). - Non chiamare metodi forniti dall'utente o eseguire I/O nel metodo – leggi solo i campi.
- Se è necessaria la validazione, eseguila dopo la deserializzazione, al di fuori di
readObject()
. - Preferisci implementare
Externalizable
e fare letture esplicite dei campi invece della serializzazione predefinita. - Registra un
ObjectInputFilter
rinforzato anche per i servizi interni (design resistente ai compromessi).
Riferimenti
- Avviso di Sicurezza di Spring – CVE-2023-34040 Deserializzazione Java in Spring-Kafka (Ago 2023)
- GitHub Security Lab – GHSL-2023-044: Deserializzazione non sicura nel client Java Aerospike (Lug 2023)
tip
Impara e pratica il hacking AWS:HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica il hacking GCP: HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica il hacking Azure:
HackTricks Training Azure Red Team Expert (AzRTE)
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord o al gruppo telegram o seguici su Twitter 🐦 @hacktricks_live.
- Condividi trucchi di hacking inviando PR ai HackTricks e HackTricks Cloud repos github.