Βασική Java αποσειριοποίηση με ObjectInputStream readObject
Tip
Μάθε & εξασκήσου στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθε & εξασκήσου στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθε & εξασκήσου στο Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Περιηγήσου στον πλήρη κατάλογο HackTricks Training για τα assessment tracks (ARTA/GRTA/AzRTA) και στο Linux Hacking Expert (LHE).
Υποστήριξε το HackTricks
- Δες τα subscription plans!
- Γίνε μέλος της 💬 Discord group, της telegram group, ακολούθησε το @hacktricks_live στο X/Twitter, ή δες τη LinkedIn page και το YouTube channel.
- Μοιράσου hacking tricks υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Σε αυτήν την POST θα εξηγηθεί ένα παράδειγμα που χρησιμοποιεί java.io.Serializable και γιατί η υπερφόρτωση του readObject() μπορεί να είναι εξαιρετικά επικίνδυνη αν η εισερχόμενη ροή ελέγχεται από επιτιθέμενο.
Serializable
Η Java Serializable interface (java.io.Serializable) είναι ένα marker interface που οι κλάσεις σας πρέπει να υλοποιήσουν αν πρόκειται να είναι σειριοποιημένες και αποσειριοποιημένες. Η Java object serialization (εγγραφή) γίνεται με το ObjectOutputStream και η deserialization (ανάγνωση) γίνεται με το ObjectInputStream.
Υπενθύμιση: Ποιες μέθοδοι καλούνται έμμεσα κατά την αποσειριοποίηση;
readObject()– class-specific read logic (if implemented and private).readResolve()– can replace the deserialized object with another one.validateObject()– viaObjectInputValidationcallbacks.readExternal()– for classes implementingExternalizable.- Constructors are not executed – therefore gadget chains rely exclusively on the previous callbacks.
Οποιαδήποτε μέθοδος σε αυτή την αλυσίδα που καταλήγει να χρησιμοποιεί δεδομένα ελεγχόμενα από επιτιθέμενο (εκτέλεση εντολών, JNDI lookups, reflection, κ.λπ.) μετατρέπει τη διαδικασία αποσειριοποίησης σε RCE gadget.
Ας δούμε ένα παράδειγμα με μια class Person η οποία είναι serializable. Αυτή η κλάση overwrites the readObject function, έτσι όταν any object αυτής της class is deserialized αυτή η function πρόκειται να εκτελεστεί.
Στο παράδειγμα, η readObject function της κλάσης Person καλεί τη συνάρτηση eat() του κατοικίδιού του και η συνάρτηση eat() ενός Dog (για κάποιο λόγο) καλεί ένα calc.exe. Θα δούμε πώς να σειριοποιήσουμε και να αποσειριοποιήσουμε ένα αντικείμενο Person για να εκτελέσουμε αυτόν τον calculator:
The following example is from https://medium.com/@knownsec404team/java-deserialization-tool-gadgetinspector-first-glimpse-74e99e493649
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");
}
}
Conclusion (classic scenario)
Όπως φαίνεται στο πολύ απλό αυτό παράδειγμα, η «ευπάθεια» εδώ προκύπτει επειδή η μέθοδος readObject() καλεί άλλο attacker-controlled code. Σε πραγματικά gadget chains, χιλιάδες κλάσεις που περιέχονται σε εξωτερικές βιβλιοθήκες (Commons-Collections, Spring, Groovy, Rome, SnakeYAML, κ.λπ.) μπορούν να κακοποιηθούν – ο attacker χρειάζεται μόνο ένα προσβάσιμο gadget για να αποκτήσει code execution.
2023-2025: Τι άλλαξε σε πραγματικά Java deserialization bugs;
Πρόσφατες υποθέσεις υπενθυμίζουν ότι τα σφάλματα σε ObjectInputStream δεν είναι πλέον απλώς «ανέβασε ένα .ser αρχείο σε ένα legacy HTTP endpoint»:
- Broker / queue consumers: Το Spring-Kafka (
CVE-2023-34040) έδειξε ότι το deserializing exception headers από attacker-controlled topics είναι αρκετό εάν ο consumer ενεργοποιεί τα ασυνήθιστα flagscheckDeserExWhen*. - Client-side trust of remote servers: Ο Aerospike Java client (
CVE-2023-36480) deserialized objects που λαμβάνονται από τον server. Η αντίδραση του vendor ήταν αξιοσημείωτη: οι νεότεροι clients αφαίρεσαν την υποστήριξη Java runtime serialization/deserialization αντί να προσπαθήσουν να τη διατηρήσουν πίσω από ένα αδύναμο φίλτρο. - “Restricted” streams are often still too broad: Το
pac4j-core(CVE-2023-25581) προσπάθησε να προστατεύσει το deserialization μεRestrictedObjectInputStream, αλλά το αποδεκτό σύνολο κλάσεων ήταν ακόμα αρκετά μεγάλο ώστε να επιτρέπεται η εκμετάλλευση gadget.
Το επιθετικό μάθημα είναι ότι το επικίνδυνο trust boundary συχνά δεν είναι «ο χρήστης ανεβάζει ένα blob», αλλά «κάποιο component που ο developer θεωρούσε trusted μπορεί να εγχύσει bytes σε ένα stream που τελικά φτάνει στο readObject()».
Αν χρειάζεστε low-noise reachability checks πριν αφιερώσετε χρόνο σε full gadget research, χρησιμοποιήστε τις αφιερωμένες Java σελίδες για:
Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner
readObject() anti-patterns που εξακολουθούν να δημιουργούν gadget entrypoints
Ακόμα και αν η κλάση σας δεν είναι προφανές RCE gadget, τα παρακάτω μοτίβα αρκούν για να γίνει εκμεταλλεύσιμη όταν attacker-controlled objects είναι ενσωματωμένα στο graph:
- Κλήση overridable μεθόδων ή interface μεθόδων από
readObject()(pet.eat()στο PoC παραπάνω είναι το κλασικό παράδειγμα). - Εκτέλεση lookups, reflection, class loading, expression evaluation, ή JNDI operations κατά το deserialization.
- Επανάληψη πάνω σε attacker-controlled collections ή maps, που μπορεί να ενεργοποιήσουν
hashCode(),equals(), comparators, ή transformers ως side effects. - Εγγραφή
ObjectInputValidationcallbacks που εκτελούν επικίνδυνη post-processing. - Η υπόθεση ότι «private
readObject()» αρκεί ως προστασία. Ελέγχει μόνο τη dispatch semantics· δεν κάνει το deserialization ασφαλές.
Modern mitigations που πρέπει να εφαρμόσετε
- JEP 290 / Serialization Filtering (Java 9+) Χρησιμοποιήστε allow-list και explicit graph limits:
-Djdk.serialFilter="com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;maxbytes=16384;!*"
- Apply a filter on every untrusted stream, not just globally:
try (var ois = new ObjectInputStream(input)) {
var filter = ObjectInputFilter.Config.createFilter(
"com.example.dto.*;java.base/*;maxdepth=5;maxrefs=1000;!*"
);
ois.setObjectInputFilter(filter);
return (Message) ois.readObject();
}
- JEP 415 (Java 17+) Context-Specific Filter Factories Προτιμήστε αυτό όταν η ίδια JVM έχει πολλαπλά deserialization contexts (RMI, cache replication, message consumers, admin-only imports) και καθένα χρειάζεται διαφορετικό allow-list.
- Keep
readObject()boring Καλείτε μόνοdefaultReadObject()/ explicit field reads, και μετά εκτελέστε αυστηρούς ελέγχους invariants. Μην κάνετε I/O, μην καταγράφετε που dereference attacker-controlled objects, μην κάνετε dynamic lookups ή method calls σε deserialized sub-objects. - If possible, remove Java native serialization from the design
Η διόρθωση του Aerospike είναι καλό μοντέλο: όταν το feature δεν είναι απαραίτητο, η διαγραφή της χρήσης
readObject()/writeObject()είναι συχνά πιο ασφαλής από το να προσπαθείς να διατηρήσεις τέλεια φίλτρα επ’ αόριστον.
Detection and research workflow
ysoserialπαραμένει η βάση για gadget validation και quick RCE/URLDNS probes.marshalsecεξακολουθεί να είναι χρήσιμο όταν ο sink pivots σε JNDI/LDAP/RMI περιοχή.GadgetInspectorείναι χρήσιμο όταν έχετε τα target jars και χρειάζεται να ψάξετε για application-specific gadget chains.- Το Java 17 πρόσθεσε το
jdk.DeserializationFlight Recorder event, χρήσιμο για να δείτε πού χρησιμοποιείται πραγματικά τοObjectInputStreamκαι αν εφαρμόζονται φίλτρα.
Quick checklist για secure readObject() implementations
- Κάντε τη μέθοδο
privateκαι σχολιάστε τα serialization hooks με@Serialώστε οι compilers να πιάσουν λάθη σε mis-declared signatures. - Καλέστε
defaultReadObject()πρώτα εκτός αν έχετε ισχυρό λόγο να διαβάσετε χειροκίνητα όλο το object graph. - Θεωρήστε κάθε nested object ως attacker-controlled μέχρι να επικυρωθεί.
- Μην επικαλείστε μεθόδους σε deserialized collaborators από μέσα σε
readObject(). - Συνδυάστε τον code review με έναν ObjectInputFilter review· «φαίνονται ασφαλείς
readObject()κώδικες» δεν αρκούν αν το stream εξακολουθεί να δέχεται αυθαίρετες κλάσεις.
References
- OpenJDK JEP 415: Context-Specific Deserialization Filters
- GitHub Security Lab: GHSL-2022-085 / CVE-2023-25581 (
pac4j-coredeserialization leading to RCE)
Tip
Μάθε & εξασκήσου στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθε & εξασκήσου στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθε & εξασκήσου στο Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Περιηγήσου στον πλήρη κατάλογο HackTricks Training για τα assessment tracks (ARTA/GRTA/AzRTA) και στο Linux Hacking Expert (LHE).
Υποστήριξε το HackTricks
- Δες τα subscription plans!
- Γίνε μέλος της 💬 Discord group, της telegram group, ακολούθησε το @hacktricks_live στο X/Twitter, ή δες τη LinkedIn page και το YouTube channel.
- Μοιράσου hacking tricks υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.


