Basic Java Deserialization with ObjectInputStream readObject

Reading time: 7 minutes

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

Σε αυτήν την ανάρτηση θα εξηγηθεί ένα παράδειγμα χρησιμοποιώντας java.io.Serializable και γιατί η υπερχείλιση του readObject() μπορεί να είναι εξαιρετικά επικίνδυνη αν το εισερχόμενο ρεύμα ελέγχεται από επιτιθέμενο.

Serializable

Η διεπαφή Java Serializable (java.io.Serializable) είναι μια διεπαφή σήμανσης που οι κλάσεις σας πρέπει να υλοποιούν αν θέλουν να σειριοποιηθούν και αποσειριοποιηθούν. Η σειριοποίηση αντικειμένων Java (γραφή) γίνεται με το ObjectOutputStream και η αποσειριοποίηση (ανάγνωση) γίνεται με το ObjectInputStream.

Υπενθύμιση: Ποιες μέθοδοι καλούνται έμμεσα κατά την αποσειριοποίηση;

  1. readObject() – λογική ανάγνωσης συγκεκριμένης κλάσης (αν έχει υλοποιηθεί και είναι ιδιωτική).
  2. readResolve() – μπορεί να αντικαταστήσει το αποσειριοποιημένο αντικείμενο με ένα άλλο.
  3. validateObject() – μέσω callbacks ObjectInputValidation.
  4. readExternal() – για κλάσεις που υλοποιούν Externalizable.
  5. Οι κατασκευαστές δεν εκτελούνται – επομένως οι αλυσίδες gadget βασίζονται αποκλειστικά στους προηγούμενους callbacks.

Οποιαδήποτε μέθοδος σε αυτήν την αλυσίδα που καταλήγει να καλεί δεδομένα ελεγχόμενα από επιτιθέμενο (εκτέλεση εντολών, αναζητήσεις JNDI, ανακλαστικότητα κ.λπ.) μετατρέπει τη διαδικασία αποσειριοποίησης σε gadget RCE.

Ας δούμε ένα παράδειγμα με μια κλάση Person που είναι σειριοποιήσιμη. Αυτή η κλάση υπερκαλύπτει τη λειτουργία readObject, έτσι όταν οποιοδήποτε αντικείμενο αυτής της κλάσης είναι αποσειριοποιημένο, αυτή η λειτουργία θα εκτελείται.
Στο παράδειγμα, η λειτουργία 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 παραβιάστηκε – οι κακόβουλες απαντήσεις του διακομιστή περιείχαν σειριακούς φορτίους που αποσυμπιέστηκαν από τον 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);
  1. JEP 415 (Java 17+) Φίλτρα Ειδικών Συγκείμενων – χρησιμοποιήστε έναν BinaryOperator<ObjectInputFilter> για να εφαρμόσετε διαφορετικά φίλτρα ανά εκτελεστικό συγκείμενο (π.χ., ανά κλήση RMI, ανά καταναλωτή ουράς μηνυμάτων).
  2. Μην εκθέτετε τον ακατέργαστο ObjectInputStream μέσω του δικτύου – προτιμήστε κωδικοποιήσεις JSON/Δυαδικών χωρίς σημασιολογία εκτέλεσης κώδικα (Jackson μετά την απενεργοποίηση του DefaultTyping, Protobuf, Avro, κ.λπ.).
  3. Περιορισμοί Άμυνας σε Βάθος – Ορίστε μέγιστο μήκος πίνακα, βάθος, αναφορές:
bash
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
  1. Συνεχής σάρωση 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)

tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE) Μάθετε & εξασκηθείτε στο Azure Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks