Serialization can be used maliciously. Examples include using serialization to maliciously , for example, to violate the intended invariants of a class. Deserialization is equivalent to object construction; consequently, all invariants enforced during object construction must also be enforced during deserialization. The default serialized form lacks any enforcement of class invariants; consequently, programs must not use the default serialized form for any class with implementation-defined invariants.
The deserialization process creates a new instance of the class without invoking any of the class's constructors. Consequently, any input validation checks present within the in constructors are bypassed. Moreover, transient and static fields may fail to reflect their true values because such fields are bypassed during the serialization procedure and consequently cannot be restored from the object stream. As a result, any class with transient fields , and any class that performs validation checks in its constructors , must also perform similar validation checks when being deserialized.
Validating deserialized objects establishes that the object state is within defined limits and ensures that all transient and static fields have their default secure values. Fields However, fields that are declared final and contain a constant value will contain the proper value after deserialization rather than the default value after serialization. For example, the value of the field private transient final n = 42
after deserialization will be 42 after deserialization rather than 0. Deserialization produces default values for all other cases.
...
Wiki Markup |
---|
In this noncompliant code example \[[Bloch 2005|AA. Bibliography#Bloch 05]\], a class with singleton semantics uses the default serialized form, which fails to enforce any implementation-defined invariants. Consequently, the malicious code can create a second instance even though the class should have only a single instance. For purposes of this example, we assume that the class contains only nonsensitive data. |
Code Block | ||
---|---|---|
| ||
public class SingletonClassNumberData extends ExceptionNumber { // ...implement abstract Number methods, publiclike Number.doubleValue()... private static final SingletonClassNumberData INSTANCE = new NumberData SingletonClass(); public static NumberData getInstance() { return INSTANCE; } private SingletonClassNumberData() { // Perform security checks and parameter validation } protected int printData() { int data = 1000; // print data return data; } } class Malicious { public static void main(String[] args) { SingletonClassNumberData sc = (SingletonClassNumberData) deepCopy(SingletonClassNumberData.INSTANCEgetInstance()); // Prints false; indicates new instance System.out.println(sc == SingletonClassNumberData.INSTANCEgetInstance()); // Prints false; indicates new instance System.out.println("Balance = " + sc.printData()); } // This method should not be used in production quality code static public Object deepCopy(Object obj) { try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); new ObjectOutputStream(bos).writeObject(obj); ByteArrayInputStream bin = new ByteArrayInputStream(bos.toByteArray()); return new ObjectInputStream(bin).readObject(); } catch (Exception e) { throw new IllegalArgumentException(e); } } } |
Compliant Solution
This compliant solution uses an enum
and adds a custom readResolve()
method that replaces the deserialized instance with a reference to the appropriate singleton from the current execution. More complicated cases may also require custom writeObject()
or readObject()
methods in addition to (or instead of) the custom readResolve()
method.
Code Block | ||
---|---|---|
| ||
Classpublic enum NumberEnumSingletonClass extends Number { INSTANCE; NumberData number = new NumberData(); More information on correctly handling singleton classes is available in |
...
therule MSC07-J |
...
Code Block | ||
---|---|---|
| ||
, âPrevent multiple instantiations of singleton objects.â class SingletonClass extends Number { Exception { // ... private protected final Object readResolve() throws NotSerializableException { return INSTANCE; } } public class NumberData extends Number { // ... } |
This compliant solution uses composition over extension of the Number
class. More information on singleton classes is available in rule MSC07-J..
Noncompliant Code Example
This noncompliant code example uses a custom-defined readObject()
method but fails to perform input validation after deserialization. The design of the system requires the maximum value of any lottery ticket to be 20,000; however. However, an attacker can manipulate the serialized array to generate a different number on deserialization.
...
Code Block | ||
---|---|---|
| ||
public final class Lottery implements Serializable { // ... private synchronized void readObject(java.io.ObjectInputStream s) throws IOException, ClassNotFoundException { ObjectInputStream.GetField fields = s.readFields(); int ticket = fields.get("ticket", 0); if (ticket > 20000 || ticket <= 0) { throw new InvalidObjectException("Not in range!"); } // Validate draw this.ticket = ticket; } } |
Note that the class must also be declared final to prevent a malicious subclass from carrying out a finalizer attack. (See rule OBJ11-J. Be wary of letting constructors throw exceptions.) For extendable classes, an acceptable alternative is to use a flag that indicates whether the instance is safe for use. The flag can be set after validation and must be checked in every method before any operation is performed.
Additionally, any transient or static fields must be explicitly set to an appropriate value within readObject()
.This compliant solution must also validate that draw
is a secure random number generator. If an attacker made draw
generate insufficiently random numbers, the attacker could control the future tickets generated by roll()
.
Note that this compliant solution is insufficient to protect sensitive data. See rule SER03-J. Do not serialize unencrypted, sensitive data for additional information.
...
Code Block | ||
---|---|---|
| ||
public class Lottery implements Serializable { private transient int ticket = 1; private transient SecureRandom draw = new SecureRandom(); public Lottery(int ticket) { this.ticket = (int) (Math.abs(ticket % 20000) + 1); } public int getTicket() { return this.ticket; } public int roll() { this.ticket = (int) ((Math.abs(draw.nextInt()) % 20000) + 1); return this.ticket; } public static void main(String[] args) { Lottery l = new Lottery(2); for(int i = 0; i < 10; i++) { l.roll(); System.out.println(l.getTicket()); } } private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { in.defaultReadObject(); this.draw = new SecureRandom(); roll(); } } |
Compliant Solution (Non-
...
serializable)
This compliant solution simply does not mark the Lottery
class serializable.
Code Block | ||
---|---|---|
| ||
public final class Lottery { // ... } |
Risk Assessment
Serializing objects Using the default serialized form for any class with implementation-defined characteristics can corrupt the state of the objectinvariants may result in the malicious tampering of class invariants.
Rule | Severity | Likelihood | Remediation Cost | Priority | Level |
---|---|---|---|---|---|
SER07-J | medium | probable | high | P4 | L3 |
...
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="79e9012234cee96a-e0d82ba0-478f4d93-bd05911b-92fa6a7e4a8b118b67293482"><ac:plain-text-body><![CDATA[ | [[API 2006 | AA. Bibliography#API 06]] | Class | ]]></ac:plain-text-body></ac:structured-macro> |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="be83eaace1598dc2-534c9756-4ae6432c-a0c2a7c4-ff9a64406148afb3095f4cb8"><ac:plain-text-body><![CDATA[ | [[Bloch 2008 | AA. Bibliography#Bloch 08]] | Item 75: ", Consider using a custom serialized form " | ]]></ac:plain-text-body></ac:structured-macro> |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="a1cdc90c587c5a73-b5f03630-4c6b4a83-818aa28c-90519781e6af286eb6168cb2"><ac:plain-text-body><![CDATA[ | [[Greanier 2000 | AA. Bibliography#Greanier 00]] |
| ]]></ac:plain-text-body></ac:structured-macro> |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="931f51c332547699-e4e74b08-4fac4346-a80c9d79-7540e5f391422cf20df6e8d1"><ac:plain-text-body><![CDATA[ | [[Harold 1999 | AA. Bibliography#Harold 99]] | Chapter 11: , Object Serialization, Validation | ]]></ac:plain-text-body></ac:structured-macro> |
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="4c8555ad2354544d-5ec0f18f-4f3b42df-b9d9a5a2-46e84578f88e1d143a721867"><ac:plain-text-body><![CDATA[ | [[Hawtin 2008 | AA. Bibliography#Hawtin 08]] | Antipattern 8: . Believing deserialisation is unrelated to construction | ]]></ac:plain-text-body></ac:structured-macro> |
...