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 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 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.
Data Deserialization of data that is trusted , but must cross a trust boundary (perhaps because it originates from a different host) should be signed and sealed to maintain its trust. See 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.
...
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 class of the object (and of all sub-objects) is either a GoodClass1
or a GoodClass2
. The WhitelistedObjectInputStream
class here is compatible with the strategy employed by the compliant solution in SEC58-J. Deserialization methods should not perform potentially dangerous operations by default.
Code Block | ||||
---|---|---|---|---|
| ||||
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; } } |
...
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 |
---|
CodeSonar |
| JAVA.CLASS.SER.ND | Serialization Not Disabled (Java) | ||||||
Parasoft Jtest |
| CERT.SER12.VOBD | Validate objects before deserialization | ||||||
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] |
[Terse 2015] | Terse Systems, Closing the Open Door of Java Object Serialization, Nov 8, 2015 |