Article: The State of Java Serialization

MMS Founder

Article originally posted on InfoQ. Visit InfoQ

Key Takeaways

  • Java serialization introduces security flaws across many libraries.
  • Discussions are open to modularize serialization.
  • If serialization becomes a module, developers would be able to remove from the attack surface.
  • Removing other modules removes their risk.
  • Instrumentation offers a way to weave security controls in, offering a present-day defense.

Java’s Serialization feature has garnered several years worth of security exploits and zero day attacks, earning it the nickname, “the gift that keeps on giving” and “the fourth unforgivable curse“.

As a response, the OpenJDK contributor team has discussed ways to limit access to serialization, such as extracting it into a jigsaw module that can be removed – the idea being that hackers cannot attack what isn’t there.

This follows up on recommendations from popular articles, such as “Serialization Must Die” and would help prevent exploits in popular software like VCenter 6.5.

What is serialization?

Serialization has existed in the Java platform since the release JDK 1.1 in 1997.

It was intended as a lightweight mechanism to share object representation between sockets or store an object and its state for future retrieval (a.k.a. deserialization).

Within JDK 10 and below, serialization is included in all systems as a part of java.base and the java.io.Serializable methods.

GeeksForGeeks maintains a description of how serialization works at its base.

For more code examples of how Serialization is used, Baeldung has an Introduction to Java Serialization.

Challenges & limitations of serialization

Serialization is limited in two primary ways:

  1. Newer strategies for object transfer have emerged, such as JSON, XML, Apache Avro, Protocol Buffers, and others.
  2. The serialization strategy from 1997 could not foresee the way that modern internet services would be built and attacked.

The basic premise of serialization attacks is looking for classes that execute or perform a privileged action on data that gets deserialized, then passing them the representation of that class with a malicious payload. To understand the complete walkthrough, the presentation “Exploiting Deserialization Vulnerabilities in Java” by Matthias Kaiser in 2015 provides a sample starting on slide 14.

Much of the security research surrounding serialization is based on work by Chris Frohoff, Gabriel Lawrence, and Alvaro Munoz.

Where is serialization and how do I know if my application uses it?

The change to remove serialization stems from the java.io package, which is part of the java.base module. The most common uses are:

Developers using these methods should consider switching to alternative methods of storing and reading back their data. Eishay Smith has posted performance metrics of several different serialization libraries. When evaluating performance, an awareness of security needs to be incorporated into the baseline metric. In cases where the default Java serialization is “faster”, the exploits operate at the same speed.

How can we limit serialization flaws today?

Isolation of the serialization APIs is discussed in project Amber. The idea is that is serialization moves from java.base to its own module, applications can remove it altogether. The discussion of this proposal did not bear fruit in the time for the JDK 11 feature set but may be worked on for a future Java version.

Limit serialization exposure now through runtime protection

Many organizations will benefit from a system which can monitor for risks, automating repeatable security expertise. Java applications can embed security monitoring through tools that take advantage of the JVMTI, using instrumentation to place sensors inside an application. One free product in this space is Contrast Security, a previous Duke’s Choice award winner from JavaOne. Similar to other software projects such as MySQL or GraalVM, Contrast Security’s community edition is free to developers.

The benefit of using runtime instrumentation for Java security is that it does not require code changes and can integrate directly into the JRE.
It operates similarly to Aspect Oriented Programming, weaving nonintrusive bytecode into the application at sources (where remote data enters the application), sinks (where data can be used in an unsafe way), and passthroughs (where security tracking needs to move from one Object to another).

By integrating at each “sink”, such as serialization’s ObjectInputStream, runtime protection can add its functionality. This functionality was critical for serialization back before deserialization filters were backported from JDK 9 and remains crucial for other attack types such as SQL Injection.

Integrating this runtime protection simply involves changing the startup flag to add a javaagent. In Tomcat, for example, this flag is entered into bin/setenv.sh as follows:


Once startup, Tomcat will then initiate and weave detection and prevention into the application as it loads. The separation of concerns leaves applications free to focus on their business logic while the security analyzer handles the right security at the right place.

Other helpful security technology

For ongoing maintenance, instead of wondering or making a manual list, consider running a system like OWASP Dependency-Check, which will identify dependencies with known security flaws and indicate were to upgrade. Or rather than waiting, consider automatically keeping up to date with libraries through a system like DependABot.

Although well intentioned, the default Oracle Serialization Filter suffers from from the same design flaw as the SecurityManager and associated sandbox exploits. By conflating personas and requiring advance knowledge of the un-knowable, this feature will likely see limited adoption: system administrators do not know code to list class files, developers do not know the environment, and even DevOps teams typically do not know requirements of other system parts such as the application server.

Removing security risks of unused modules

One aspect of the modular JDK of Java 9 is the ability to create custom runtime images that remove unnecessary modules, cutting them out with a tool called jlink. The security benefit of this approach is that hackers cannot attack what isn’t there.

Although the proposal to modularize serialization may take time for applications to adopt and use the new feature of using alternate serialization, fixing security issues follows the proverb of planting a tree: “the best time to plant a tree was twenty years ago, the second best time is now.”

Switching away from Java’s native serialization should also offer better interoperability for most applications and microservices. By using a standards-based format, such as JSON or XML, developers can more easily communicate between services written in different languages – a python microservice typically has better integrations for reading JSON document than a binary blob from Java 7 update X. While the JSON format simplifies object sharing, the Friday the 13th JSON attacks against Java and .NET parsers demonstrate that there is no silver bullet (whitepaper).

Until that switch is made, serialization will remain in java.base. It is possible, however, to decrease the risk associated with other modules, and this same technique will apply if and when serialization is modularized.

Example of modularizing JDK 10 for Apache Tomcat 8.5.31

In this example, we will modularize a JRE to run Apache Tomcat and prune out any JDK modules that are not needed. The result will be a custom JRE, slimmed down with a decreased attack surface, that is still able to run the application.

Identify which modules are needed

The first step for many applications is to examine which modules are actually used by the application. The OpenJDK tools jdeps can perform a bytecode scan of JAR files and list these modules. Like most users, I do not know which dependencies or modules are needed by code that I did not write. As a result, I will use scanners to detect and report this information to me.

The command to list modules required by a single JAR file is:

jdeps -s JarFile.jar

This will give basic output listing the modules:

tomcat-coyote.jar -> java.base
tomcat-coyote.jar -> java.management
tomcat-coyote.jar -> not found

Ultimately each of these modules (listed on the right) should go into a module file which creates the base module of your application. This file is called module-info.java with a hyphen, indicating special treatment that does not follow standard Java conventions.

Rather than manually copy each JAR file included with Tomcat, the following chain of commands will enumerate all modules into a usable file. Run this command from the root Tomcat directory:

find . -name *.jar ! -path "./webapps/*" ! -path "./temp/*" -exec jdeps -s {} ; | sed -En "s/.* -> (.*)/  requires 1;/p" | sort | uniq | grep -v "not found" | xargs -0 printf "module com.infoq.jdk.TomcatModuleExample{n%s}n"

The output of that command will go into lib/module-info.java and look like this:

module com.infoq.jdk.TomcatModuleExample{
  requires java.base;
  requires java.compiler;
  requires java.desktop;
  requires java.instrument;
  requires java.logging;
  requires java.management;
  requires java.naming;
  requires java.security.jgss;
  requires java.sql;
  requires java.xml.ws.annotation;
  requires java.xml.ws;
  requires java.xml;

Looking at that module listing, it is much shorter than the entire list of Java modules.

The next step is to place this module-info into its own JAR:

javac lib/module-info.java
jar -cf lib/Tomcat.jar lib/module-info.class

Finally, create a JRE specifically for this application:

jlink --module-path lib:$JAVA_HOME/jmods --add-modules ThanksInfoQ_Costlow --output dist

The output of that command is a runtime with just enough modules needed to run the application, without any performance overhead or security risks from unused modules.

To see the list, run the command on the standard JRE and the one you just created inside the dist directory. Compared to the base JDK 10, only 19 of the core 98 modules remain.

java --list-modules


After running this command, the application can now be run using the runtime inside the dist folder.

Looking at this list: the deployment plugin (applets) is gone, JDBC (SQL) is gone, JavaFX is gone, and so are many other modules. From a performance perspective, these modules can no longer have an impact. From a security perspective, hackers cannot attack what isn’t there. Accuracy is more important than pruning. It is important to keep modules that your application needs because if they are missing, the application will not work either.

About the Author

Erik Costlow was Oracle’s principal product manager for Java 8 and 9, focused on security and performance. His security expertise involves threat modeling, code analysis, and instrumentation of security sensors. He working to is broaden this approach to security with Contrast Security. Before becoming involved in technology, Erik was a circus performer who juggled fire on a three-wheel vertical unicycle.

Subscribe for MMS Newsletter

By signing up, you will receive updates about our latest information.

  • This field is for validation purposes and should be left unchanged.