Object serialization is the process of saving an object's state as a sequence of bytes; deserialization is the process rebuilding the object from those bytes. The primary application of serialization is in Java Remote Method Invocation (RMI) wherein objects are packed (marshalled), exchanged between distributed virtual machines, and unpacked (unmarshalled). It also finds extensive use in Java Beans.
Once a serializable class has been exported, future refactoring of its code often becomes problematic. Specifically, existing serialized forms (encoded representations) become part of the object's published API and must be supported for an indefinite period. This can be troublesome from a security perspective; not only does it promote dead code, it also commits the provider to potentially eternally maintenance of a compatible codebase.
Classes that implement Serializable
and fail to override its functionality are said to be using the default serialized form. In the event of future changes to the class, byte streams produced by users of old versions of the class will be incompatible with the new implementation. Programs must maintain serialization compatibility during class evolution. An acceptable approach is the use of a custom serialized form, which relieves the implementer of the necessity to maintain the original serialized form and the corresponding version of the class in addition to the newly evolved version.
Noncompliant Code Example
This noncompliant code example implements a GameWeapon
class with a serializable field called noOfWeapons
, and uses the default serialization form. Any changes to the internal representation of the class can break the existing serialized form.
class GameWeapon implements Serializable { int noOfWeapons = 10; public String toString() { return String.valueOf(noOfWeapons); } }
Compliant Solution (serialVersionUID
)
In this solution, the class has a serialVersionUID
which contains a number unique to this version of the class. Any attempt to deserialize a stream with a GameWeapon
object of a different version will yield an exception.
class GameWeapon implements Serializable { private static final long serialVersionUID = 24L; int noOfWeapons = 10; public String toString() { return String.valueOf(noOfWeapons); } }
Compliant Solution
Ideally, implement Serializable
only when the class is not expected to evolve frequently. One way to maintain the original serialized form, at the same time allowing the class to evolve is to use custom serialization with the help of serialPersistentFields
. The static
and transient
fields allow one to specify what should not be serialized whereas the serialPersistentFields
field specifies what should be serialized. It also relieves the class from defining the serializable field within the class implementation, decoupling the current implementation from the overall logic. New fields can easily be added without breaking compatibility across releases.
class WeaponStore implements Serializable { int noOfWeapons = 10; // Total number of weapons } public class GameWeapon implements Serializable { WeaponStore ws = new WeaponStore(); private static final ObjectStreamField[] serialPersistentFields = {new ObjectStreamField("ws", WeaponStore.class)}; private void readObject(ObjectInputStream ois) throws IOException { try { ObjectInputStream.GetField gf = ois.readFields(); this.ws = (WeaponStore) gf.get("ws", ws); } catch (ClassNotFoundException e) { /* Forward to handler */ } } private void writeObject(ObjectOutputStream oos) throws IOException { ObjectOutputStream.PutField pf = oos.putFields(); pf.put("ws", ws); oos.writeFields(); } public String toString() { return String.valueOf(ws); } }
Exceptions
SER00-EX1: According to the Serialization Specification [[Sun 2006]], Section 1.5, "Defining Serializable Fields for a Class"
Inner classes can only contain
final static
fields that are initialized to constants or expressions built up from constants. Consequently, it is not possible to setserialPersistentFields
for an inner class (though it is possible to set it forstatic
member classes).
Finally, serialization is easy to get wrong and must consequently be carefully implemented.
Risk Assessment
Failure to provide a consistent serialization mechanism across releases can limit the extensibility of classes. If classes are extended, compatibility issues may result.
Guideline |
Severity |
Likelihood |
Remediation Cost |
Priority |
Level |
---|---|---|---|---|---|
SER00-J |
low |
probable |
high |
P2 |
L3 |
Automated Detection
Automated detection of classes that use the default serialized form is straightforward.
Related Vulnerabilities
Search for vulnerabilities resulting from the violation of this guideline on the CERT website.
Bibliography
[[API 2006]]
[[Sun 2006]] "Serialization specification", "1.5 Defining Serializable Fields for a Class" and "1.7 Accessing Serializable Fields of a Class"
[[Bloch 2008]] Item 74: "Implement serialization judiciously"
[[Harold 2006]] 13.7.5. serialPersistentFields
[[MITRE 2009]] CWE ID 589 "Call to Non-ubiquitous API"
16. Serialization (SER) 16. Serialization (SER) SER01-J. Limit the accessibility of readObject and writeObject methods