Introducing JDK 9 deserialization filter – Java I/O: Context-Specific Deserialization Filters
127. Introducing JDK 9 deserialization filter
As you know from Chapter 4, Problem X, deserialization is exposed to vulnerabilities that may cause serious security issues. In other words, between a serialization–deserialization cycles, an untrusted process (attacker) can modify/alter the serialization form to execute arbitrary code, sneak malicious data, and so on.In order to prevent such vulnerabilities, JDK 9 has introduced the possibility to create restrictions via filters meant to accept/reject deserialization based on specific predicates. A deserialization filter intercepts a stream that expects to be deserialized and applies to it one or more predicates that should be successfully passed in order to proceed with deserialization. If a predicate fails then deserialization doesn’t even start and the stream is rejected.There are two kinds of filters:
JVM-wide filters: Filters applied to every deserialization that takes place in the JVM. The behavior of these filters is tightly coupled with how they are combined with other filters (if any).
Stream filters: Filters that operates on all ObjectInputStream of an application (stream-global filters) or on certain ObjectInputStream (stream-specific filters).
We can create the following types of filters:
Filters based on patterns (known as pattern-based filters): These filters can be used to filter modules, packages, or classes via string patterns. They can be applied without touching the code (as JVM-wide filters) or they can be created via the ObjectInputFilter API (as pattern-based stream filters).
Filters based on the ObjectInputFilter API: This API allows us to define filters directly in code. Usually, such filters are defined based on string patterns or Java Reflection.
Pattern-based filters
Let’s see several filters based on string patterns. For instance, this filter accepts all classes from package foo (and from any other package that is not buzz) and rejects all classes from package buzz (a class that passes a pattern that starts with “!” is rejected):
foo.*;!buzz.*
Patterns are delimited via semicolons, “;” and white spaces are considered as part of the pattern.The following filter rejects only the class modern.challenge.Melon:
!modern.challenge.Melon
The following filter rejects the class Melon from package modern.challenge and accepts all other classes from this package (the “*” is the wildcard used to represent unspecified class/package/module names):
!modern.challenge.Melon;modern.challenge.*;!*
The following filter accepts all classes from package foo and its sub-packages (notice the “**” wildcard):
foo.**
The following filter accepts all classes starting with Hash:
Hash*
Besides filtering classes, packages, and modules, we can also define the so-called resource filters, which allow us to accept/reject resources based on object’s graph complexity and size. In this context, we have maxdepth (the maximum graph depth), maxarray (the maximum array size), maxrefs(the maximum number of references between objects of a graph), and maxbytes (the maximum number of stream bytes). Here is an example:
maxdepth=5;maxarray=1000;maxrefs=50 foo.buzz.App
Now, let’s see how we can use such filters.
Apply a pattern-based filter per application
If we want to apply a pattern-based filter to a single run of an application then we can rely on the jdk.serialFilter system property. Without touching the code, we use this system property at the command line as in the following example:
java -Djdk.serialFilter=foo.**;Hash* foo.buzz.App
A system property replaces a Security Property value.
Apply a pattern-based filter to all applications in a process
For applying a pattern-based filter to all applications in a process we should follow two steps (again, we don’t touch the application code):
Open in an editor (for instance, Notepad, Wordpad) the java.security file. In JDK 6-8 this file is located in $JAVA_HOME/lib/security/java.security while in JDK 9+, is in $JAVA_HOME/conf/security/java.security.
Edit this file by appending the pattern to the jdk.serialFilter Security Property.
Done!
ObjectInputFilter-based filters
Via the ObjectInputFilter API, we can create custom filters based on string patterns and Java Reflection. These filters can be applied to certain streams (stream-specific filters) or to all streams (stream-global filters) and can be implemented as pattern-based filters, as classes, methods, or lambda expressions.First, we implement the filter via the ObjectInputFilter API. Second, we set the filter on all/certain ObjectInputStream. Setting the filter as a stream-global filter is done via ObjectInputFilter.Config.setSerialFilter(ObjectInputFilter filter). On the other hand, setting the filter as a stream-specific filter can be done via ObjectInputStream.setObjectInputFilter(ObjectInputFilter filter).For instance, creating a pattern-based filter via this API can be done by calling the Config.createFilter(String pattern) method.A custom filter defined as a class is done by implementing the ObjectInputFilter functional interface and overriding the Status checkInput(FilterInfo filterInfo) method.A custom filter defined as a method is commonly done via a static method as static ObjectInputFilter.Status someFilter(FilterInfo info) {…}.And, a custom filter defined as a lambda expression is commonly expressed as ois.setObjectInputFilter(f -> (…)), where f is ObjectInputFilter and ois is an instance of ObjectInputStream.A filter returns a status (java.io.ObjectInputFilter.Status ) which can be ALLOWED, REJECTED, or UNDECIDED.In the next problems, we will explore these statements via examples.