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. If such classes are poorly designed, such code could even call Runtime.exec()
with an attacker-supplied argument. Therefore, untrusted input to be deserialized should be validated to ensure that the serialized data contains only classes from a whitelist of expected classes. This can be done by overloading the resolveClass()
method of the ObjectInputStream
class. As an alternative to validation of the serialized data, a 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 and without using a SecurityManager
.
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/ :
class LookAheadObjectInputStream extends ObjectInputStream { public LookAheadObjectInputStream(InputStream inputStream) throws IOException { super(inputStream); } @Override protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException { switch (desc.getName()) { case "GoodClass1": break; case "GoodClass2": break; default: throw new InvalidClassException("Unexpected serialized class", desc.getName()); } return super.resolveClass(desc); } } class DeserializeExample { private static Object deserialize(byte[] buffer) throws IOException, ClassNotFoundException { Object ret = null; try (ByteArrayInputStream bais = new ByteArrayInputStream(buffer)) { try (LookAheadObjectInputStream ois = new LookAheadObjectInputStream(bais)) { ret = ois.readObject(); } } return ret; } }
Exceptions
SER12-J-EX1. Trusted serialized data does not need be validated, provided that the code has clear documentation that it relies on the serialized data being trusted. 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.
Risk Assessment
Whether a violation of this rule is exploitable depends on what classes are on the JVM's classpath. (Note that 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 overload resolveClass()
to compare against a whitelist.
Related Guidelines
Bibliography
http://www.ibm.com/developerworks/library/se-lookahead/