VNX-JAVA-005 – Insecure Deserialization
Overview
This rule detects indicators of Java object deserialization without allowlisting — specifically ObjectInputStream, readObject(), readUnshared(), XMLDecoder, XStream, enableDefaultTyping(), and activateDefaultTyping(). Deserializing untrusted byte streams through any of these mechanisms can trigger arbitrary code execution via gadget chains: sequences of legitimate library classes whose deserialization callbacks can be chained together to achieve effects such as command execution, class loading from remote URLs, or file system access. This is CWE-502.
Severity: Critical | CWE: CWE-502 – Deserialization of Untrusted Data
Why This Matters
Java’s native serialization protocol (aced 0005 magic bytes) is a remote code execution primitive when common libraries are on the classpath. The 2015 Apache Commons Collections exploit demonstrated that any application using ObjectInputStream.readObject() on attacker data while having Commons Collections on the classpath was trivially exploitable — regardless of what the application thought it was deserializing. Similar gadget chains exist for Spring, Hibernate, Groovy, and many other ubiquitous libraries.
The impact is typically unauthenticated remote code execution with the JVM’s OS-level privileges. High-profile real-world exploits include the 2017 WebLogic mass exploitation events (CVE-2017-3248, CVE-2019-2725), the JBoss/Jenkins gadget chain exploits, and the continuous stream of Jackson polymorphic deserialization CVEs. The same class of vulnerability underpins the Log4Shell exploit chain, which used JNDI lookups initiated during deserialization.
What Gets Flagged
The rule matches any .java file containing one of the high-risk identifiers.
// FLAGGED: ObjectInputStream without filter
ObjectInputStream ois = new ObjectInputStream(request.getInputStream());
Object obj = ois.readObject(); // gadget chain execution possible
// FLAGGED: XMLDecoder deserializes arbitrary Java objects
XMLDecoder decoder = new XMLDecoder(request.getInputStream());
Object result = decoder.readObject();
// FLAGGED: XStream without security configuration
XStream xstream = new XStream();
Object data = xstream.fromXML(request.getReader());
// FLAGGED: Jackson with dangerous default typing
ObjectMapper mapper = new ObjectMapper();
mapper.enableDefaultTyping(); // deprecated, known-vulnerable
Remediation
Apply a Java serialization filter (
ObjectInputFilter). Introduced in Java 9 and backported to Java 8u121,ObjectInputFilterlets you implement an allowlist of classes that may be deserialized. The JVM rejects any class not on the list before its constructor runs, breaking gadget chains.// SAFE: allowlist-based ObjectInputFilter ObjectInputStream ois = new ObjectInputStream(inputStream); ois.setObjectInputFilter(filterInfo -> { Class<?> cls = filterInfo.serialClass(); if (cls == null) return ObjectInputFilter.Status.UNDECIDED; if (cls == MyTransferObject.class || cls == AnotherSafeClass.class) { return ObjectInputFilter.Status.ALLOWED; } return ObjectInputFilter.Status.REJECTED; }); MyTransferObject obj = (MyTransferObject) ois.readObject();Configure a JVM-wide serialization filter. Set the
jdk.serialFiltersystem property or$JAVA_HOME/conf/security/java.securityto apply a global allowlist to allObjectInputStreaminstances in the application:# java.security or -Djdk.serialFilter= jdk.serialFilter=com.example.MyTransferObject;!*Replace native serialization with a data-only format. JSON (Jackson with
FAIL_ON_UNKNOWN_PROPERTIESenabled and default typing disabled), Protocol Buffers, or Avro transmit data without triggering arbitrary class constructors. This is the most robust long-term fix.// SAFE: Jackson without polymorphic default typing ObjectMapper mapper = new ObjectMapper(); mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true); // Do NOT call enableDefaultTyping() or activateDefaultTyping() MyDto dto = mapper.readValue(json, MyDto.class);Configure XStream with a security framework. If you must use XStream, call
xstream.allowTypes()or useXStream.setupDefaultSecurity(xstream)followed by an explicit allowlist before processing any external input.Use the serialization kill-switch. On Java 8u261+ you can set
-Djdk.disableLastUsageTrackingand rely on the JVM’s built-in filter; alternatively, addNotSerializableExceptionMappingwrappers around untrusted data entry points.Deploy the OWASP Java Serialization Security library or SerialKiller as a drop-in
ObjectInputStreamreplacement for legacy code you cannot refactor immediately.Monitor for the
aced 0005magic bytes at WAF and network layer. Blocking raw Java serialization payloads at the perimeter provides defence-in-depth while code is being remediated.
References
- CWE-502: Deserialization of Untrusted Data
- CAPEC-586: Object Injection
- MITRE ATT&CK T1059 – Command and Scripting Interpreter
- OWASP Deserialization Cheat Sheet
- JEP 290 – Filter Incoming Serialization Data
- NVD CVE-2015-7501 – Commons Collections gadget chain
- OWASP Top 10 A08:2021 – Software and Data Integrity Failures
- OWASP ASVS V5 – Validation, Sanitization and Encoding