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 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 from , perhaps specified in a whitelist of expected trusted classes. This can be done by overloading 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.
...
This non-compliant code deserializes a byte array without first validating what classes will be created and without using a SecurityManager
. If the object being deserialized belongs to a class with a lax readObject()
method, this could result in remote code execution.
Code Block | ||||
---|---|---|---|---|
| ||||
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; } } |
...
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
.
Code Block | ||||
---|---|---|---|---|
| ||||
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; } } |
...