A Serializable
class can overload the
method, which is called when an object of that class is being deserialized. Both this method and the method Serializable
.readObject()readResolve()
must treat the serialized data as potentially malicious and must refrain from performing potentially dangerous operations, unless the programmer has expressly indicated whitelisted the class for the particular deserialization at hand that the class should be permitted to perform potentially dangerous operations.
This rule complements rule SER12-J. Prevent deserialization of untrusted classes. Whereas SER12-J requires the programmer to ensure the absence of classes that might perform dangerous operations by validating data before deserializing it, SER13-J requires that all serializable classes refrain, by default, from performing dangerous operations during deserialization. SER12-J and SER13-J both guard against the same class of deserialization vulnerabilities. Theoretically, a given system is secure against this class of vulnerabilities if either (1) all deployed code on that system follows SER12-J or (2) all deployed code on that system follows SER13-J. However, because much existing code violates both of these rules, the CERT Coding Standard takes the "belt and suspenders" approach and requires compliant code to follow both rules.
For the purposes of complying with SER13-J, it is permitted to to assume that, if an ObjectInputStream
contains a whitelist, then control will pass to the readObject
or readResolve
method of a class C only if C is on the whitelist. In other words, class C does not need to check that it appears on the whitelist; it only needs to check that a whitelist exists. This eliminates the need to perform a redundant check against the whitelist, and it enables compatibility with a greater range of whitelist implementations.
Non-Compliant Code Example
...
Code Block | ||||
---|---|---|---|---|
| ||||
import java.io.*;
class OpenedFile implements Serializable {
public String filename;
public BufferedReader reader;
public OpenedFile(String _filename) throws FileNotFoundException {
filename = _filename;
init();
}
private void init() throws FileNotFoundException {
reader = new BufferedReader(new FileReader(filename));
}
private void writeObject(ObjectOutputStream out) throws IOException {
out.writeUTF(filename);
}
private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException {
filename = in.readUTF();
init();
}
} |
Compliant Solution 1
In this compliant solution, the readObject()
method throws an exception unless the potentially dangerous class was whitelisted in the manner used for the compliant solution of SER12-J. Prevent deserialization of untrusted classes. The reasoning is that a programmer who expressly whitelists a class for deserialization can reasonably be presumed to be aware of what the whitelisted class does during deserialization. For example, for a web app, whitelisting the above OpenedFile
class might be considered an acceptable risk when deserializing data from an authenticated adminstrator but an unacceptable risk when deserializing data from an untrusted Internet user.Note that the following is protected by a whitelist. Note that this compliant solution for SER13-J is different from and complementary to the compliant solution in SER12-J. In the compliant solution for SER12-J, the source code location that invokes deserialization is modified to use a custom subclass of ObjectInputStream
. This subclass overrides the resolveClass()
method to check whether class of the serialized object is whitelisted before that class's readObject()
method gets called. In contrast, in the compliant solution below for SER13-J, the presence of a whitelist is checked inside the readObject()
method of the dangerous serializable class. When both solutions are used together, a minor downside is that there is some redundancy, since the whitelist is checked twice (once before readObject()
, and once during readObject()
) even though a single check would have sufficed. However, the upside is that it now requires mistakes in two different places of the code to yield an exploitable vulnerability.
Code Block | ||||
---|---|---|---|---|
| ||||
import java.io.*; import java.lang.reflect.*; class OpenedFile implements Serializable { public String filename; public BufferedReader reader; public OpenedFile(String _filename) throws FileNotFoundException { filename = _filename; init(); } private void init() throws FileNotFoundException { reader = new BufferedReader(new FileReader(filename)); } private void writeObject(ObjectOutputStream out) throws IOException { out.writeUTF(filename); } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { boolean isWhitelistedhasWhitelist = false; try { Object whitelist = in.getClass().getDeclaredField("whitelist").get(in); hasWhitelist = true; } catch (ReflectiveOperationException e) {} if (!hasWhitelist) { Method contains = whitelist throw new SecurityException("Deserialization without a whitelist is disallowed for class " + this.getClass().getMethod("contains", new Class[]{Object.class}getName() + "."); } isWhitelistedfilename = contains.invoke(whitelist, this.getClass().getName()).equals(Boolean.TRUE); } catch (ReflectiveOperationException e) {} if (!isWhitelisted)in.readUTF(); init(); } } |
Compliant Solution 2
In this compliant solution, potentially dangerous operations are moved outside of deserialization, and users of the class are required to make a separate call to init()
after deserializing.
Code Block | ||||
---|---|---|---|---|
| ||||
import java.io.*; class OpenedFile implements Serializable { public String filename; public BufferedReader reader; public OpenedFile(String _filename) throws FileNotFoundException { filename = _filename; } public void init() throws FileNotFoundException { reader = thrownew new SecurityException("Attempted to deserialize unexpected class."BufferedReader(new FileReader(filename)); } private void writeObject(ObjectOutputStream out) throws IOException { out.writeUTF(filename); } private void }readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { filename = in.readUTF(); init(); } } |
Risk Assessment
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
SER13-J | High | Likely | High | P9 | L2 |
...