Basic Java Deserialization with ObjectInputStream readObject
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the π¬ Discord group or the telegram group or follow us on Twitter π¦ @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.
In this POST it's going to be explained an example using java.io.Serializable
and why overriding readObject()
can be extremely dangerous if the incoming stream is attacker-controlled.
Serializable
The Java Serializable
interface (java.io.Serializable
) is a marker interface your classes must implement if they are to be serialized and deserialized. Java object serialization (writing) is done with the ObjectOutputStream
and deserialization (reading) is done with the ObjectInputStream
.
Reminder: Which methods are implicitly invoked during deserialization?
readObject()
β class-specific read logic (if implemented and private).readResolve()
β can replace the deserialized object with another one.validateObject()
β viaObjectInputValidation
callbacks.readExternal()
β for classes implementingExternalizable
.- Constructors are not executed β therefore gadget chains rely exclusively on the previous callbacks.
Any method in that chain that ends up invoking attacker-controlled data (command execution, JNDI lookups, reflection, etc.) turns the deserialization routine into an RCE gadget.
Lets see an example with a class Person which is serializable. This class overwrites the readObject function, so when any object of this class is deserialized this function is going to be executed.
In the example, the readObject function of the class Person calls the function eat()
of his pet and the function eat()
of a Dog (for some reason) calls a calc.exe. We are going to see how to serialize and deserialize a Person object to execute this 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)
As you can see in this very basic example, the βvulnerabilityβ here appears because the readObject() method is calling other attacker-controlled code. In real-world gadget chains, thousands of classes contained in external libraries (Commons-Collections, Spring, Groovy, Rome, SnakeYAML, etc.) can be abused β the attacker only needs one reachable gadget to get code execution.
2023-2025: Whatβs new in Java deserialization attacks?
- 2023 β CVE-2023-34040: Spring-Kafka deserialization of error-record headers when
checkDeserExWhen*
flags are enabled allowed arbitrary gadget construction from attacker-published topics. Fixed in 3.0.10 / 2.9.11. ΒΉ - 2023 β CVE-2023-36480: Aerospike Java client trusted-server assumption broken β malicious server replies contained serialized payloads that were deserialized by the client β RCE. Β²
- 2023 β CVE-2023-25581:
pac4j-core
user profile attribute parsing accepted{#sb64}
-prefixed Base64 blobs and deserialized them despite aRestrictedObjectInputStream
. Upgrade β₯ 4.0.0. - 2023 β CVE-2023-4528: JSCAPE MFT Manager Service (port 10880) accepted XML-encoded Java objects leading to RCE as root/SYSTEM.
- 2024 β Multiple new gadget chains were added to ysoserial-plus(mod) including Hibernate5, TomcatEmbed, and SnakeYAML 2.x classes that bypass some old filters.
Modern mitigations you should deploy
- JEP 290 / Serialization Filtering (Java 9+)
Add an allow-list or deny-list of classes:
Programmatic example:# Accept only your DTOs and java.base, reject everything else -Djdk.serialFilter="com.example.dto.*;java.base/*;!*"
var filter = ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" ); ObjectInputFilter.Config.setSerialFilter(filter);
- JEP 415 (Java 17+) Context-Specific Filter Factories β use a
BinaryOperator<ObjectInputFilter>
to apply different filters per execution context (e.g., per RMI call, per message queue consumer). - Do not expose raw
ObjectInputStream
over the wire β prefer JSON/Binary encodings without code execution semantics (Jackson after disablingDefaultTyping
, Protobuf, Avro, etc.). - Defense-in-Depth limits β Set maximum array length, depth, references:
-Djdk.serialFilter="maxbytes=16384;maxdepth=5;maxrefs=1000"
- Continuous gadget scanning β run tools such as
gadget-inspector
orserialpwn-cli
in your CI to fail the build if a dangerous gadget becomes reachable.
Updated tooling cheat-sheet (2024)
ysoserial-plus.jar
β community fork with > 130 gadget chains:java -jar ysoserial-plus.jar CommonsCollections6 'calc' | base64 -w0
marshalsec
β still the reference for JNDI gadget generation (LDAP/RMI).gadget-probe
β fast black-box gadget discovery against network services.SerialSniffer
β JVMTI agent that prints every class read byObjectInputStream
(useful to craft filters).- Detection tip β enable
-Djdk.serialDebug=true
(JDK 22+) to log filter decisions and rejected classes.
Quick checklist for secure readObject()
implementations
- Make the method
private
and add the@Serial
annotation (helps static analysis). - Never call user-supplied methods or perform I/O in the method β only read fields.
- If validation is needed, perform it after deserialization, outside of
readObject()
. - Prefer implementing
Externalizable
and do explicit field reads instead of default serialization. - Register a hardened
ObjectInputFilter
even for internal services (compromise-resilient design).
References
- Spring Security Advisory β CVE-2023-34040 Java Deserialization in Spring-Kafka (Aug 2023)
- GitHub Security Lab β GHSL-2023-044: Unsafe Deserialization in Aerospike Java Client (Jul 2023)
tip
Learn & practice AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Learn & practice GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Learn & practice Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)
Support HackTricks
- Check the subscription plans!
- Join the π¬ Discord group or the telegram group or follow us on Twitter π¦ @hacktricks_live.
- Share hacking tricks by submitting PRs to the HackTricks and HackTricks Cloud github repos.