Skip to main content
GitHub

SER12-J. Prevent deserialization of untrusted data

Deserializing untrusted data can cause Java to create an object of an arbitrary attacker-specified class, provided that the class is available on the classpath specified for the JVM.  Some classes have triggers that execute additional code when they are created in this manner; see SEC58-J. Deserialization methods should not perform potentially dangerous operations for more information.  If such classes are poorly designed, such code could even invoke arbitrary methods, such as Runtime.exec() with an attacker-supplied argument.  Therefore, untrusted input to be deserialized should be first validated to ensure that the serialized data contains only trusted classes, perhaps specified in a whitelist of trusted classes.  This can be done by overriding the resolveClass() method of the java.io.ObjectInputStream class.

This rule applies only to untrusted data. Data that does not cross a program's trust boundary is, by definition, trusted and can be deserialized without violating this rule. See the exception SER12-EX0 for more information.

Deserialization of data that is trusted but must cross a trust boundary (perhaps because it originates from a different host) automatically complies with this rule, but must also comply with SER02-J. Sign then seal objects before sending them outside a trust boundary for more information.

Non-Compliant Code Example

This non-compliant code deserializes a byte array without first validating what classes will be created. If the object being deserialized belongs to a class with a lax readObject() method, this could result in remote code execution.

Non-compliant code
import java.io.*;

class DeserializeExample {
  public static Object deserialize(byte[] buffer) throws IOException, ClassNotFoundException {
    Object ret = null;
    try (ByteArrayInputStream bais = new ByteArrayInputStream(buffer)) {
      try (ObjectInputStream ois = new ObjectInputStream(bais)) {
        ret = ois.readObject();
      }
    }
    return ret;
  }
}

Compliant Solution (Look-Ahead Java Deserialization)

This compliant solution is based on https://www.ibm.com/developerworks/library/se-lookahead/ . It inspects the class of any object being deserialized, before its readObject() method is invoked. The code consequently throws an InvalidClassException unless the class of the object (and of all sub-objects) is either a GoodClass1 or a GoodClass2 .

Compliant code
import java.io.*;
import java.util.*;

class WhitelistedObjectInputStream extends ObjectInputStream {
  public Set whitelist;

  public WhitelistedObjectInputStream(InputStream inputStream, Set wl) throws IOException {
    super(inputStream);
    whitelist = wl;
  }
 
  @Override
  protected Class<?> resolveClass(ObjectStreamClass cls) throws IOException, ClassNotFoundException {
    if (!whitelist.contains(cls.getName())) {
      throw new InvalidClassException("Unexpected serialized class", cls.getName());
    }
    return super.resolveClass(cls);
  }
}
 
class DeserializeExample {
  private static Object deserialize(byte[] buffer) throws IOException, ClassNotFoundException {
    Object ret = null;
    Set whitelist = new HashSet<String>(Arrays.asList(new String[]{"GoodClass1","GoodClass2"}));
    try (ByteArrayInputStream bais = new ByteArrayInputStream(buffer)) {
      try (WhitelistedObjectInputStream ois = new WhitelistedObjectInputStream(bais, whitelist)) {
        ret = ois.readObject();
      }
    }
    return ret;
  }
}

It might appear that the above compliant solution violates Rule OBJ09-J. Compare classes and not class names .  However, the security issue addressed by that rule is applicable only when comparing the class of an object that might have been loaded by a foreign ClassLoader , i.e., an object x for which it cannot be guaranteed that x.getClass()==Class.forName(x.getClass().getName()) .  In WhitelistedObjectInputStream.resolveClass() (which is the method that does the comparison of class names), a check could be added to verify that the return value (let's call it " ret ") is such that ret == Class.forName(ret.getName()) , but this check would always succeed, so it is pointless to add.

On a somewhat related point, it may be noted that ObjectInputStream.resolveClass() compares the serialVersionUID from the serialized data to the serialVersionUID of the Class object that it is going to return; if there is a mismatch, it throws an exception.

Whitelist

The construction of a suitable whitelist is itself a real challenge. Remote-code-execution exploits have been constructed, not only for classes in the Apache Commons Collection, but also for such core classes as java.util.concurrent.AtomicReferenceArray ( CVE 2012-0507 ), and even simple classes like java.util.HashSet [Terse 2015] and java.net.URL [Terse 2015] can cause a JVM to hang or exhaust memory.

Exceptions

SER12-EX0: Serialized data from a trusted input source does not require sanitization, provided that the code clearly documents that it relies on the input source being trustworthy.  For example, if a library is being audited, a routine of that library may have a documented precondition that its callers pre-sanitize any passed-in serialized data or confirm the input source as trustworthy.

CERT Vulnerability # 576313 describes a family of exploitable vulnerabilities that arise from violating this rule.

Risk Assessment

Whether a violation of this rule is exploitable depends on what classes are on the JVM's classpath.  (Note that this is a property of the execution environment, not of the code being audited.) In the worst case, it could lead to remote execution of arbitrary code.

RuleSeverityLikelihoodDetectableRepairablePriorityLevel
SER12-JHighLikelyNoNoP9L2

Automated Detection

ToolVersionCheckerDescription
CodeSonar
9.2p0
JAVA.CLASS.SER.NDSerialization not disabled (Java)
Parasoft Jtest
2026.1
CERT.SER12.VOBDValidate objects before deserialization
PVS-Studio
7.43
V5333
Security Reviewer - Static Reviewer
6.02
UnsafeDeserializationFull Implementation
ysoserialUseful for developing exploits that detect violation of this rule

It should not be difficult to write a static analysis to check for deserialization that fails to override resolveClass() to compare against a whitelist.

MITRE CWECWE-502 , Deserialization of Untrusted Data

Bibliography

[ API 2014 ]Interface Serializable
https://www.ibm.com/developerworks/library/se-lookahead/
https://foxglovesecurity.com/2015/11/06/what-do-weblogic-websphere-jboss-jenkins-opennms-and-your-application-have-in-common-this-vulnerability/
Terse 2015Terse Systems, Closing the Open Door of Java Object Serialization , Nov 8, 2015