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 SER13-J. Treat data to be deserialized as potentially malicious by default 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 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. As an alternative to validation of the serialized data, a java.lang.SecurityManager
can be used to perform deserialization in a less-privileged context.
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.
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
This compliant solution is based on http://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 object (and all sub-objects) is a GoodClass1
or a GoodClass2
. The WhitelistedObjectInputStream
class here is compatible with the strategy employed by the compliant solution in SER13-J. Treat data to be deserialized as potentially malicious by default.
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 OBJ-09J 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.)
Exceptions
SER12-EX0: Serialized data from a trusted input source does not require validation, 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-validate any passed-in serialized data or confirm the input source as trustworthy.
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.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
SER12-J | High | Likely | High | P9 | L2 |
Automated Detection
Tool | Version | Checker | Description |
---|---|---|---|
Useful 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.
Related Guidelines
Bibliography
[API 2014] | |
| |
|