Osnovna Java deserializacija sa ObjectInputStream readObject
Tip
Nauči i vežbaj AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Nauči i vežbaj GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Nauči i vežbaj Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Pregledaj kompletan HackTricks Training katalog za assessment tracks (ARTA/GRTA/AzRTA) i Linux Hacking Expert (LHE).
Podrži HackTricks
- Pogledaj pretplatničke planove!
- Pridruži se 💬 Discord grupi, telegram grupi, prati @hacktricks_live na X/Twitter, ili pogledaj LinkedIn stranicu i YouTube kanal.
- Deli hacking trikove slanjem PR-ova u HackTricks i HackTricks Cloud github repozitorijume.
U ovom POST-u biće objašnjen primer koji koristi java.io.Serializable i zašto prepisivanje readObject() može biti izuzetno opasno ako je dolazeći stream pod kontrolom napadača.
Serializable
Java Serializable interface (java.io.Serializable) je marker interfejs koji vaše klase moraju implementirati ako treba da budu serializovane i deserializovane. Java object serialization (pisanje) se obavlja pomoću ObjectOutputStream, a deserialization (čitanje) se obavlja pomoću ObjectInputStream.
Podsetnik: Koji se metodi implicitno pozivaju tokom deserializacije?
readObject()– class-specific read logic (if implemented and private).readResolve()– može zameniti deserializovani objekat drugim.validateObject()– putemObjectInputValidationcallbacks.readExternal()– za klase koje implementirajuExternalizable.- Konstruktori se ne izvršavaju – stoga gadget chains zavise isključivo od prethodnih callbacks.
Bilo koji metod u tom lancu koji dovodi do pozivanja podataka pod kontrolom napadača (command execution, JNDI lookups, reflection, itd.) pretvara rutinu deserializacije u RCE gadget.
Pogledajmo primer sa class Person koji je serializable. Ova klasa overwrites the readObject funkciju, tako da kada se bilo koji objekat ove klase deserializuje, ova funkcija će biti izvršena.
U primeru, readObject funkcija klase Person poziva funkciju eat() svog ljubimca, a funkcija eat() klase Dog (iz nekog razloga) pokreće calc.exe. Videćemo kako da serializujemo i deserializujemo Person objekat da bismo pokrenuli ovaj kalkulator:
Sledeći primer je iz 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");
}
}
Zaključak (klasični scenario)
Kao što vidite u ovom vrlo osnovnom primeru, “vulnerabilnost” ovde se pojavljuje zato što metoda readObject() poziva drugi kod koji je pod kontrolom napadača. U realnim gadget lancima, hiljade klasa iz eksternih biblioteka (Commons-Collections, Spring, Groovy, Rome, SnakeYAML, itd.) mogu biti zloupotrebljene – napadaču je dovoljan jedan dostižan gadget da dobije izvršavanje koda.
2023-2025: Šta se promenilo u realnim Java deserialization bagovima?
Nedavni slučajevi podsećaju da bagovi u ObjectInputStream više nisu samo “upload .ser fajla na legacy HTTP endpoint”:
- Broker / queue consumers: Spring-Kafka (
CVE-2023-34040) je pokazao da deserializacija exception header-a iz topika koje kontroliše napadač može biti dovoljna ako consumer uključi neuobičajenecheckDeserExWhen*flag-ove. - Client-side trust of remote servers: Aerospike Java client (
CVE-2023-36480) je deserializovao objekte primljene od servera. Reakcija vendora je bila značajna: noviji client-i su uklonili podršku za Java runtime serialization/deserialization umesto da pokušaju da je sačuvaju iza slabog filtera. - “Restricted” streams are often still too broad:
pac4j-core(CVE-2023-25581) je pokušao da zaštiti deserializaciju saRestrictedObjectInputStream, ali skup prihvaćenih klasa je i dalje bio dovoljno veliki da omogući zloupotrebu gadgeta.
Ofanzivna lekcija je da je opasna granica poverenja često nije “korisnik uploaduje blob”, već “neki komponent koji je developer smatrao pouzdanim može ubaciti bajtove u stream koji na kraju stiže do readObject()”.
Ako trebate low-noise provere dostižnosti pre nego što uložite vreme u kompletno istraživanje gadgeta, koristite posvećene Java stranice za:
Java DNS Deserialization, GadgetProbe and Java Deserialization Scanner
readObject() anti-patterni koji i dalje kreiraju ulazne tačke za gadgete
Čak i ako vaša klasa sama po sebi nije očigledan RCE gadget, sledeći paterni su dovoljni da je učine eksploatabilnom kada su objekti pod kontrolom napadača ugnježdeni u graf:
- Pozivanje overridable metoda ili metoda interfejsa iz
readObject()(pet.eat()u PoC iznad je klasičan primer). - Izvođenje lookup-a, reflection-a, učitavanja klasa, evaluacije izraza ili JNDI operacija tokom deserializacije.
- Iteriranje preko kolekcija ili mapa koje kontroliše napadač, što može pokrenuti
hashCode(),equals(), comparatore ili transformere kao sporedne efekte. - Registracija
ObjectInputValidationcallback-ova koji vrše opasnu post-obradu. - Pretpostavka da je “private
readObject()” dovoljna zaštita. Ona samo kontroliše dispatch semantiku; ne čini deserializaciju bezbednom.
Moderni mitigations koje treba da uložite
- JEP 290 / Serialization Filtering (Java 9+) Koristite allow-listu i eksplicitna ograničenja grafa:
-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 Preferirajte ovo kada ista JVM ima više konteksta deserializacije (RMI, cache replication, message consumers, admin-only imports) i svaki zahteva drugačiju allow-listu.
- Keep
readObject()boring Pozovite samodefaultReadObject()/ eksplicitno čitanje polja, pa zatim izvedite stroge provere invariantnosti. Ne radite I/O, logovanje koje dereferencira objekte koje kontroliše napadač, dinamičke lookup-e ili pozive metoda na deserializovanim pod-objektima. - If possible, remove Java native serialization from the design
Aerospike-ovo rešenje je dobar model: kada funkcionalnost nije neophodna, brisanje upotrebe
readObject()/writeObject()često je sigurnije nego pokušaj da se zauvek održavaju savršeni filteri.
Detection and research workflow
ysoserialostaje osnov za validaciju gadgeta i brze RCE/URLDNS probe.marshalsecje i dalje koristan kada sink pivotuje u JNDI/LDAP/RMI teritoriju.GadgetInspectorje koristan kada imate target jar-ove i treba da tražite application-specific gadget lance.- Java 17 je dodao
jdk.DeserializationFlight Recorder event, koji pomaže da se vidi gde seObjectInputStreamzaista koristi i da li se filteri primenjuju.
Quick checklist for secure readObject() implementations
- Napravite metodu
privatei anotirajte serialization hook-ove sa@Serialtako da kompajleri mogu uhvatiti pogrešno deklarisane signaturne. - Pozovite
defaultReadObject()prvo osim ako imate jak razlog da ručno čitate ceo objektni graf. - Tretirajte svaki ugnježdeni objekat kao pod kontrolom napadača dok nije validiran.
- Nikada ne pozivajte metode na deserializovanim saradnicima iznutra
readObject(). - Uskladite code review sa pregledom
ObjectInputFilter; “naizgled bezbedanreadObject()kod” nije dovoljan ako stream i dalje prihvata proizvoljne klase.
References
- OpenJDK JEP 415: Context-Specific Deserialization Filters
- GitHub Security Lab: GHSL-2022-085 / CVE-2023-25581 (
pac4j-coredeserialization leading to RCE)
Tip
Nauči i vežbaj AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Nauči i vežbaj GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Nauči i vežbaj Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Pregledaj kompletan HackTricks Training katalog za assessment tracks (ARTA/GRTA/AzRTA) i Linux Hacking Expert (LHE).
Podrži HackTricks
- Pogledaj pretplatničke planove!
- Pridruži se 💬 Discord grupi, telegram grupi, prati @hacktricks_live na X/Twitter, ili pogledaj LinkedIn stranicu i YouTube kanal.
- Deli hacking trikove slanjem PR-ova u HackTricks i HackTricks Cloud github repozitorijume.


