mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
155 lines
8.5 KiB
Markdown
155 lines
8.5 KiB
Markdown
# Grundlegende Java-Deserialisierung mit ObjectInputStream readObject
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|
||
|
||
In diesem POST wird ein Beispiel erklärt, das `java.io.Serializable` verwendet **und warum das Überschreiben von `readObject()` extrem gefährlich sein kann, wenn der eingehende Stream vom Angreifer kontrolliert wird**.
|
||
|
||
## Serializable
|
||
|
||
Das Java `Serializable`-Interface (`java.io.Serializable`) ist ein Marker-Interface, das Ihre Klassen implementieren müssen, wenn sie **serialisiert** und **deserialisiert** werden sollen. Die Java-Objektserialisierung (Schreiben) erfolgt mit dem [`ObjectOutputStream`](http://tutorials.jenkov.com/java-io/objectoutputstream.html) und die Deserialisierung (Lesen) erfolgt mit dem [`ObjectInputStream`](http://tutorials.jenkov.com/java-io/objectinputstream.html).
|
||
|
||
### Erinnerung: Welche Methoden werden während der Deserialisierung implizit aufgerufen?
|
||
|
||
1. `readObject()` – klassen-spezifische Lese-Logik (wenn implementiert und *privat*).
|
||
2. `readResolve()` – kann das deserialisierte Objekt durch ein anderes ersetzen.
|
||
3. `validateObject()` – über `ObjectInputValidation`-Callbacks.
|
||
4. `readExternal()` – für Klassen, die `Externalizable` implementieren.
|
||
5. Konstruktoren werden **nicht** ausgeführt – daher verlassen sich Gadget-Ketten ausschließlich auf die vorherigen Callbacks.
|
||
|
||
Jede Methode in dieser Kette, die letztendlich Angreifer-kontrollierte Daten aufruft (Befehlsausführung, JNDI-Abfragen, Reflection usw.), verwandelt die Deserialisierungsroutine in ein RCE-Gadget.
|
||
|
||
Lassen Sie uns ein Beispiel mit einer **Klasse Person** sehen, die **serialisierbar** ist. Diese Klasse **überschreibt die readObject**-Funktion, sodass, wenn **irgendein Objekt** dieser **Klasse** **deserialisiert** wird, diese **Funktion** **ausgeführt** wird.\
|
||
Im Beispiel ruft die **readObject**-Funktion der Klasse Person die Funktion `eat()` seines Haustiers auf, und die Funktion `eat()` eines Hundes (aus irgendeinem Grund) ruft eine **calc.exe** auf. **Wir werden sehen, wie man ein Person-Objekt serialisiert und deserialisiert, um diesen Rechner auszuführen:**
|
||
|
||
**Das folgende Beispiel stammt von <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");
|
||
}
|
||
}
|
||
```
|
||
### Fazit (klassisches Szenario)
|
||
|
||
Wie Sie in diesem sehr einfachen Beispiel sehen können, tritt die „Schwachstelle“ hier auf, weil die **readObject()**-Methode **anderen vom Angreifer kontrollierten Code aufruft**. In realen Gadget-Ketten können Tausende von Klassen, die in externen Bibliotheken (Commons-Collections, Spring, Groovy, Rome, SnakeYAML usw.) enthalten sind, missbraucht werden – der Angreifer benötigt nur *ein* erreichbares Gadget, um Codeausführung zu erlangen.
|
||
|
||
---
|
||
|
||
## 2023-2025: Was gibt es Neues bei Java-Deserialisierungsangriffen?
|
||
|
||
* 2023 – CVE-2023-34040: Spring-Kafka-Deserialisierung von Fehlerdatensatz-Headern, wenn `checkDeserExWhen*`-Flags aktiviert sind, erlaubte die willkürliche Gadget-Konstruktion aus von Angreifern veröffentlichten Themen. In 3.0.10 / 2.9.11 behoben. ¹
|
||
* 2023 – CVE-2023-36480: Aerospike Java-Client vertrauenswürdige Serverannahme gebrochen – bösartige Serverantworten enthielten serialisierte Payloads, die vom Client deserialisiert wurden → RCE. ²
|
||
* 2023 – CVE-2023-25581: `pac4j-core` Benutzerprofilattribut-Parsing akzeptierte `{#sb64}`-präfixierte Base64-Blobs und deserialisierte sie trotz eines `RestrictedObjectInputStream`. Upgrade ≥ 4.0.0.
|
||
* 2023 – CVE-2023-4528: JSCAPE MFT Manager Service (Port 10880) akzeptierte XML-kodierte Java-Objekte, was zu RCE als root/SYSTEM führte.
|
||
* 2024 – Mehrere neue Gadget-Ketten wurden zu ysoserial-plus(mod) hinzugefügt, einschließlich Hibernate5, TomcatEmbed und SnakeYAML 2.x-Klassen, die einige alte Filter umgehen.
|
||
|
||
## Moderne Abwehrmaßnahmen, die Sie implementieren sollten
|
||
|
||
1. **JEP 290 / Serialisierungsfilterung (Java 9+)**
|
||
*Fügen Sie eine Erlauben- oder Verweigern-Liste von Klassen hinzu:*
|
||
```bash
|
||
# Akzeptieren Sie nur Ihre DTOs und java.base, lehnen Sie alles andere ab
|
||
-Djdk.serialFilter="com.example.dto.*;java.base/*;!*"
|
||
```
|
||
Programmierbeispiel:
|
||
```java
|
||
var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" );
|
||
ObjectInputFilter.Config.setSerialFilter(filter);
|
||
```
|
||
2. **JEP 415 (Java 17+) Kontext-spezifische Filterfabriken** – verwenden Sie einen `BinaryOperator<ObjectInputFilter>`, um unterschiedliche Filter pro Ausführungskontext anzuwenden (z. B. pro RMI-Aufruf, pro Nachrichtenwarteschlangenverbraucher).
|
||
3. **Keine rohen `ObjectInputStream` über das Netzwerk exponieren** – bevorzugen Sie JSON/Binärkodierungen ohne Codeausführungssemantik (Jackson nach Deaktivierung von `DefaultTyping`, Protobuf, Avro usw.).
|
||
4. **Defense-in-Depth-Limits** – Setzen Sie maximale Array-Länge, Tiefe, Referenzen:
|
||
```bash
|
||
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
|
||
```
|
||
5. **Kontinuierliches Gadget-Scannen** – führen Sie Tools wie `gadget-inspector` oder `serialpwn-cli` in Ihrer CI aus, um den Build zu fehlschlagen, wenn ein gefährliches Gadget erreichbar wird.
|
||
|
||
## Aktualisierte Tooling-Checkliste (2024)
|
||
|
||
* `ysoserial-plus.jar` – Community-Fork mit > 130 Gadget-Ketten:
|
||
```bash
|
||
java -jar ysoserial-plus.jar CommonsCollections6 'calc' | base64 -w0
|
||
```
|
||
* `marshalsec` – immer noch das Referenztool für JNDI-Gadget-Generierung (LDAP/RMI).
|
||
* `gadget-probe` – schnelle Black-Box-Gadget-Entdeckung gegen Netzwerkdienste.
|
||
* `SerialSniffer` – JVMTI-Agent, der jede Klasse druckt, die von `ObjectInputStream` gelesen wird (nützlich zum Erstellen von Filtern).
|
||
* **Erkennungstipp** – aktivieren Sie `-Djdk.serialDebug=true` (JDK 22+), um Filterentscheidungen und abgelehnte Klassen zu protokollieren.
|
||
|
||
## Schnelle Checkliste für sichere `readObject()`-Implementierungen
|
||
|
||
1. Machen Sie die Methode `private` und fügen Sie die `@Serial`-Annotation hinzu (hilft bei der statischen Analyse).
|
||
2. Rufen Sie niemals benutzereingereichte Methoden auf oder führen Sie I/O in der Methode aus – lesen Sie nur Felder.
|
||
3. Wenn eine Validierung erforderlich ist, führen Sie diese **nach** der Deserialisierung außerhalb von `readObject()` durch.
|
||
4. Bevorzugen Sie die Implementierung von `Externalizable` und führen Sie explizite Feldlesungen anstelle der Standardserialisierung durch.
|
||
5. Registrieren Sie einen gehärteten `ObjectInputFilter`, auch für interne Dienste (kompromissresistentes Design).
|
||
|
||
## Referenzen
|
||
|
||
1. Spring Security Advisory – CVE-2023-34040 Java-Deserialisierung in Spring-Kafka (Aug 2023)
|
||
2. GitHub Security Lab – GHSL-2023-044: Unsichere Deserialisierung im Aerospike Java-Client (Jul 2023)
|
||
|
||
{{#include ../../banners/hacktricks-training.md}}
|