Serialization and deserialization can be used to bypass security manager checks. A serializable class may employ security manager checks in its constructors for various reasons. For example, the checks prevent an attacker from modifying the internal state of the class.
Non-Compliant Code Example
In this non-compliant example, security manager checks are used within the constructor but are not replicated throughout, specifically, within the readObject
and writeObject
methods that are used in the serialization-deserialization process. This allows an attacker to maliciously create instances of the class that bypass security manager checks when deserialization is performed.
Code Block | ||
---|---|---|
| ||
public final class CreditCard implements java.io.Serializable { //private internal state private String credit_card; private static final String DEFAULT = "DEFAULT"; public CreditCard() { //initialize credit_card to default value credit_card = DEFAULT; } //allows callers to modify (private) internal state public void changeCC(String newCC) { if (credit_card.equals(newCC)) { // no change return; } else { validateInput(newCC); credit_card = newCC; } } // readObject correctly enforces checks during deserialization private readObject(java.io.ObjectInputStream in) { defaultReadObject(); // if the deserialized name does not match the default value normally // created at construction time, duplicate the checks if (!DEFAULT.equals(credit_card)) { validateInput(credit_card); } } // allows callers to retrieve internal state public String getValue() { return somePublicValue; } // writeObject correctly enforces checks during serialization private void writeObject(java.io.ObjectOutputStream out) { out.writeObject(credit_card); } } |
Compliant Solution
The compliant solution correctly implements security manager checks in all constructors, methods that can modify internal state and methods that retrieve internal state. As a result, an attacker cannot create an instance of the object with modified state (using deserialization) and cannot simply read the serialized byte stream to uncover sensitive data (using serialization).
Code Block | ||
---|---|---|
| ||
public final class SecureCreditCard implements java.io.Serializable { //private internal state private String credit_card; private static final String DEFAULT = "DEFAULT"; public SecureCreditCard() { //initialize credit_card to default value credit_card = DEFAULT; } //allows callers to modify (private) internal state public void changeCC(String newCC) { if (credit_card.equals(newCC)) { // no change return; } else { // check permissions to modify credit_card performSecurityManagerCheck(); validateInput(newCC); credit_card = newCC; } } // readObject correctly enforces checks during deserialization private readObject(java.io.ObjectInputStream in) { defaultReadObject(); // if the deserialized name does not match the default value normally // created at construction time, duplicate the checks if (!DEFAULT.equals(credit_card)) { performSecurityManagerCheck(); validateInput(credit_card); } } // allows callers to retrieve internal state public String getValue() { // check permission to get value securityManagerCheck(); return somePublicValue; } // writeObject correctly enforces checks during serialization private void writeObject(java.io.ObjectOutputStream out) { // duplicate check from getValue() securityManagerCheck(); out.writeObject(credit_card); } } |
References
Sun Secure Coding Guidelines