You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: src/pentesting-web/deserialization/basic-java-deserialization-objectinputstream-readobject.md
+52-35Lines changed: 52 additions & 35 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -100,57 +100,74 @@ As you can see in this very basic example, the “vulnerability” here appears
100
100
101
101
---
102
102
103
-
## 2023-2025: What’s new in Java deserialization attacks?
103
+
## 2023-2025: What changed in real-world Java deserialization bugs?
104
104
105
-
* 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. ¹
106
-
* 2023 – CVE-2023-36480: Aerospike Java client trusted-server assumption broken – malicious server replies contained serialized payloads that were deserialized by the client → RCE. ²
107
-
* 2023 – CVE-2023-25581: `pac4j-core` user profile attribute parsing accepted `{#sb64}`-prefixed Base64 blobs and deserialized them despite a `RestrictedObjectInputStream`. Upgrade ≥ 4.0.0.
108
-
* 2023 – CVE-2023-4528: JSCAPE MFT Manager Service (port 10880) accepted XML-encoded Java objects leading to RCE as root/SYSTEM.
109
-
* 2024 – Multiple new gadget chains were added to ysoserial-plus(mod) including Hibernate5, TomcatEmbed, and SnakeYAML 2.x classes that bypass some old filters.
105
+
Recent cases are a good reminder that `ObjectInputStream` bugs are no longer just “upload a `.ser` file to a legacy HTTP endpoint”:
106
+
107
+
***Broker / queue consumers**: Spring-Kafka (`CVE-2023-34040`) showed that deserializing exception headers from attacker-controlled topics is enough if the consumer enables the unusual `checkDeserExWhen*` flags.
108
+
***Client-side trust of remote servers**: the Aerospike Java client (`CVE-2023-36480`) deserialized objects received from the server. The vendor response was notable: newer clients removed Java runtime serialization/deserialization support instead of trying to preserve it behind a weak filter.
109
+
***“Restricted” streams are often still too broad**: `pac4j-core` (`CVE-2023-25581`) tried to protect deserialization with `RestrictedObjectInputStream`, but the accepted class set was still large enough to make gadget abuse possible.
110
+
111
+
The offensive lesson is that the dangerous trust boundary is often **not** “user uploads a blob”, but “some component the developer considered trusted can inject bytes into a stream that eventually reaches `readObject()`”.
112
+
113
+
If you need low-noise reachability checks before spending time on full gadget research, use the dedicated Java pages for:
114
+
115
+
{{#ref}}
116
+
java-dns-deserialization-and-gadgetprobe.md
117
+
{{#endref}}
118
+
119
+
## `readObject()` anti-patterns that still create gadget entrypoints
120
+
121
+
Even if your class itself is not an obvious RCE gadget, the following patterns are enough to make it exploitable when attacker-controlled objects are embedded in the graph:
122
+
123
+
1. Calling overridable methods or interface methods from `readObject()` (`pet.eat()` in the PoC above is the classic example).
124
+
2. Performing lookups, reflection, class loading, expression evaluation, or JNDI operations during deserialization.
125
+
3. Iterating over attacker-controlled collections or maps, which may trigger `hashCode()`, `equals()`, comparators, or transformers as side effects.
126
+
4. Registering `ObjectInputValidation` callbacks that perform dangerous post-processing.
127
+
5. Assuming “private `readObject()`” is enough protection. It only controls dispatch semantics; it does **not** make deserialization safe.
110
128
111
129
## Modern mitigations you should deploy
112
130
113
131
1.**JEP 290 / Serialization Filtering (Java 9+)**
114
-
*Add an allow-list or deny-list of classes:*
132
+
Use an allow-list and explicit graph limits:
115
133
```bash
116
-
# Accept only your DTOs and java.base, reject everything else
2.**Apply a filter on every untrusted stream, not just globally**:
120
137
```java
121
-
var filter =ObjectInputFilter.Config.createFilter("com.example.dto.*;java.base/*;!*" );
122
-
ObjectInputFilter.Config.setSerialFilter(filter);
123
-
```
124
-
2.**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).
125
-
3.**Do not expose raw `ObjectInputStream` over the wire** – prefer JSON/Binary encodings without code execution semantics (Jackson after disabling `DefaultTyping`, Protobuf, Avro, etc.).
126
-
4.**Defense-in-Depth limits** – Set maximum array length, depth, references:
5.**Continuous gadget scanning** – run tools such as `gadget-inspector` or `serialpwn-cli` in your CI to fail the build if a dangerous gadget becomes reachable.
Prefer this when the same JVM has multiple deserialization contexts (RMI, cache replication, message consumers, admin-only imports) and each one needs a different allow-list.
148
+
4.**Keep `readObject()` boring**
149
+
Only call `defaultReadObject()` / explicit field reads, then perform strict invariant checks. Do not do I/O, logging that dereferences attacker-controlled objects, dynamic lookups, or method calls on deserialized sub-objects.
150
+
5.**If possible, remove Java native serialization from the design**
151
+
The Aerospike fix is a good model: when the feature is not essential, deleting `readObject()` / `writeObject()` usage is often safer than trying to maintain perfect filters forever.
131
152
132
-
## Updated tooling cheat-sheet (2024)
153
+
## Detection and research workflow
133
154
134
-
*`ysoserial-plus.jar` – community fork with > 130 gadget chains:
*`marshalsec` – still the reference for JNDI gadget generation (LDAP/RMI).
139
-
*`gadget-probe` – fast black-box gadget discovery against network services.
140
-
*`SerialSniffer` – JVMTI agent that prints every class read by `ObjectInputStream` (useful to craft filters).
141
-
***Detection tip** – enable `-Djdk.serialDebug=true` (JDK 22+) to log filter decisions and rejected classes.
155
+
*`ysoserial` remains the baseline for gadget validation and quick RCE/URLDNS probes.
156
+
*`marshalsec` is still useful when the sink pivots into JNDI/LDAP/RMI territory.
157
+
*`GadgetInspector` is useful when you have the target jars and need to look for application-specific gadget chains.
158
+
* Java 17 added the `jdk.Deserialization` Flight Recorder event, which is useful for seeing where `ObjectInputStream` is actually used and whether filters are being applied.
142
159
143
160
## Quick checklist for secure `readObject()` implementations
144
161
145
-
1. Make the method `private` and add the `@Serial`annotation (helps static analysis).
146
-
2.Never call user-supplied methods or perform I/O in the method – only read fields.
147
-
3.If validation is needed, perform it **after** deserialization, outside of `readObject()`.
148
-
4.Prefer implementing `Externalizable` and do explicit field reads instead of default serialization.
149
-
5.Register a hardened `ObjectInputFilter`even for internal services (compromise-resilient design).
162
+
1. Make the method `private` and annotate serialization hooks with `@Serial`so compilers can catch mis-declared signatures.
163
+
2.Call `defaultReadObject()` first unless you have a strong reason to manually read the full object graph.
164
+
3.Treat every nested object as attacker-controlled until validated.
165
+
4.Never invoke methods on deserialized collaborators from inside `readObject()`.
166
+
5.Pair the code review with an `ObjectInputFilter`review; “safe-looking `readObject()` code” is not enough if the stream still accepts arbitrary classes.
150
167
151
168
## References
152
169
153
-
1. Spring Security Advisory – CVE-2023-34040 Java Deserialization in Spring-Kafka (Aug 2023)
0 commit comments