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

155 lines
12 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}}
Σε αυτή την ανάρτηση θα εξηγηθεί ένα παράδειγμα χρησιμοποιώντας `java.io.Serializable` **και γιατί η υπερχείλιση του `readObject()` μπορεί να είναι εξαιρετικά επικίνδυνη αν το εισερχόμενο ρεύμα ελέγχεται από επιτιθέμενο**.
## Serializable
Η διεπαφή Java `Serializable` (`java.io.Serializable`) είναι μια διεπαφή σήμανσης που οι κλάσεις σας πρέπει να υλοποιούν αν θέλουν να είναι **serializable** και **deserializable**. Η σειριοποίηση αντικειμένων Java (γραφή) γίνεται με το [`ObjectOutputStream`](http://tutorials.jenkov.com/java-io/objectoutputstream.html) και η αποσειριοποίηση (ανάγνωση) γίνεται με το [`ObjectInputStream`](http://tutorials.jenkov.com/java-io/objectinputstream.html).
### Υπενθύμιση: Ποιες μέθοδοι καλούνται έμμεσα κατά την αποσειριοποίηση;
1. `readObject()` λογική ανάγνωσης συγκεκριμένης κλάσης (αν έχει υλοποιηθεί και είναι *ιδιωτική*).
2. `readResolve()` μπορεί να αντικαταστήσει το αποσειριοποιημένο αντικείμενο με ένα άλλο.
3. `validateObject()` μέσω callbacks `ObjectInputValidation`.
4. `readExternal()` για κλάσεις που υλοποιούν `Externalizable`.
5. Οι κατασκευαστές **δεν** εκτελούνται επομένως οι αλυσίδες gadget βασίζονται αποκλειστικά στους προηγούμενους callbacks.
Οποιαδήποτε μέθοδος σε αυτή την αλυσίδα που καταλήγει να καλεί δεδομένα ελεγχόμενα από επιτιθέμενο (εκτέλεση εντολών, αναζητήσεις JNDI, ανακλαστικότητα, κ.λπ.) μετατρέπει τη ρουτίνα αποσειριοποίησης σε gadget RCE.
Ας δούμε ένα παράδειγμα με μια **κλάση Person** που είναι **serializable**. Αυτή η κλάση **υπερκαλύπτει τη συνάρτηση readObject**, έτσι όταν **οποιοδήποτε αντικείμενο** αυτής της **κλάσης** είναι **deserialized** αυτή η **συνάρτηση** θα **εκτελείται**.\
Στο παράδειγμα, η **συνάρτηση readObject** της κλάσης Person καλεί τη συνάρτηση `eat()` του κατοικίδιου του και η συνάρτηση `eat()` ενός Σκύλου (για κάποιο λόγο) καλεί ένα **calc.exe**. **Θα δούμε πώς να σειριοποιήσουμε και να αποσειριοποιήσουμε ένα αντικείμενο Person για να εκτελέσουμε αυτόν τον υπολογιστή:**
**Το παρακάτω παράδειγμα είναι από <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");
}
}
```
### Συμπέρασμα (κλασικό σενάριο)
Όπως μπορείτε να δείτε σε αυτό το πολύ βασικό παράδειγμα, η “ευπάθεια” εδώ εμφανίζεται επειδή η μέθοδος **readObject()** **καλεί άλλον κώδικα που ελέγχεται από τον επιτιθέμενο**. Σε πραγματικές αλυσίδες gadget, χιλιάδες κλάσεις που περιέχονται σε εξωτερικές βιβλιοθήκες (Commons-Collections, Spring, Groovy, Rome, SnakeYAML, κ.λπ.) μπορούν να καταχραστούν ο επιτιθέμενος χρειάζεται μόνο *μία* προσβάσιμη gadget για να αποκτήσει εκτέλεση κώδικα.
---
## 2023-2025: Τι νέο υπάρχει στις επιθέσεις αποσυμπίεσης Java;
* 2023 CVE-2023-34040: Η αποσυμπίεση κεφαλίδων σφαλμάτων του Spring-Kafka όταν είναι ενεργοποιημένες οι σημαίες `checkDeserExWhen*` επέτρεψε την αυθαίρετη κατασκευή gadget από θέματα που δημοσιεύθηκαν από τον επιτιθέμενο. Διορθώθηκε στην 3.0.10 / 2.9.11. ¹
* 2023 CVE-2023-36480: Η υπόθεση αξιόπιστου διακομιστή του Aerospike Java client παραβιάστηκε οι κακόβουλες απαντήσεις του διακομιστή περιείχαν σειριακά payloads που αποσυμπιέστηκαν από τον client → RCE. ²
* 2023 CVE-2023-25581: Η ανάλυση του χαρακτηριστικού προφίλ χρήστη του `pac4j-core` αποδέχθηκε blobs Base64 με πρόθεμα `{#sb64}` και τα αποσυμπίεσε παρά την ύπαρξη ενός `RestrictedObjectInputStream`. Αναβάθμιση ≥ 4.0.0.
* 2023 CVE-2023-4528: Η υπηρεσία JSCAPE MFT Manager (θύρα 10880) αποδέχθηκε Java αντικείμενα κωδικοποιημένα σε XML που οδήγησαν σε RCE ως root/SYSTEM.
* 2024 Προστέθηκαν πολλές νέες αλυσίδες gadget στο ysoserial-plus(mod) συμπεριλαμβανομένων των κλάσεων Hibernate5, TomcatEmbed και SnakeYAML 2.x που παρακάμπτουν ορισμένα παλιά φίλτρα.
## Σύγχρονες μετρήσεις που πρέπει να εφαρμόσετε
1. **JEP 290 / Φιλτράρισμα Σειριοποίησης (Java 9+)**
*Προσθέστε μια λίστα επιτρεπόμενων ή απαγορευμένων κλάσεων:*
```bash
# Αποδεχθείτε μόνο τα DTO σας και το java.base, απορρίψτε τα πάντα τα άλλα
-Djdk.serialFilter="com.example.dto.*;java.base/*;!*"
```
Προγραμματιστικό παράδειγμα:
```java
var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" );
ObjectInputFilter.Config.setSerialFilter(filter);
```
2. **JEP 415 (Java 17+) Φίλτρα Ειδικών Συγκείμενων** χρησιμοποιήστε έναν `BinaryOperator<ObjectInputFilter>` για να εφαρμόσετε διαφορετικά φίλτρα ανά εκτελεστικό συγκείμενο (π.χ., ανά κλήση RMI, ανά καταναλωτή ουράς μηνυμάτων).
3. **Μην εκθέτετε το raw `ObjectInputStream` μέσω του δικτύου** προτιμήστε κωδικοποιήσεις JSON/Binary χωρίς σημασιολογία εκτέλεσης κώδικα (Jackson μετά την απενεργοποίηση του `DefaultTyping`, Protobuf, Avro, κ.λπ.).
4. **Περιορισμοί Άμυνας σε Βάθος** Ορίστε μέγιστο μήκος πίνακα, βάθος, αναφορές:
```bash
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
```
5. **Συνεχής σάρωση gadget** εκτελέστε εργαλεία όπως `gadget-inspector` ή `serialpwn-cli` στο CI σας για να αποτύχει η κατασκευή αν γίνει προσβάσιμο ένα επικίνδυνο gadget.
## Ενημερωμένο cheat-sheet εργαλείων (2024)
* `ysoserial-plus.jar` κοινότητα fork με > 130 αλυσίδες gadget:
```bash
java -jar ysoserial-plus.jar CommonsCollections6 'calc' | base64 -w0
```
* `marshalsec` παραμένει η αναφορά για τη δημιουργία gadget JNDI (LDAP/RMI).
* `gadget-probe` γρήγορη ανακάλυψη gadget black-box κατά των δικτυακών υπηρεσιών.
* `SerialSniffer` JVMTI agent που εκτυπώνει κάθε κλάση που διαβάζεται από το `ObjectInputStream` (χρήσιμο για τη δημιουργία φίλτρων).
* **Συμβουλή ανίχνευσης** ενεργοποιήστε το `-Djdk.serialDebug=true` (JDK 22+) για να καταγράψετε τις αποφάσεις φίλτρου και τις απορριφθείσες κλάσεις.
## Γρήγορη λίστα ελέγχου για ασφαλείς υλοποιήσεις `readObject()`
1. Κάντε τη μέθοδο `private` και προσθέστε την αναγνώριση `@Serial` (βοηθά στην στατική ανάλυση).
2. Ποτέ μην καλείτε μεθόδους που παρέχονται από τον χρήστη ή μην εκτελείτε I/O στη μέθοδο μόνο διαβάστε πεδία.
3. Εάν απαιτείται επικύρωση, εκτελέστε την **μετά** την αποσυμπίεση, εκτός της `readObject()`.
4. Προτιμήστε να υλοποιήσετε το `Externalizable` και να κάνετε ρητές αναγνώσεις πεδίων αντί για προεπιλεγμένη σειριοποίηση.
5. Εγγραφείτε σε ένα σκληρυμένο `ObjectInputFilter` ακόμη και για εσωτερικές υπηρεσίες (σχεδίαση ανθεκτική σε παραβιάσεις).
## Αναφορές
1. Spring Security Advisory CVE-2023-34040 Java Deserialization in Spring-Kafka (Αυγ 2023)
2. GitHub Security Lab GHSL-2023-044: Unsafe Deserialization in Aerospike Java Client (Ιουλ 2023)
{{#include ../../banners/hacktricks-training.md}}