...
The deserialization process creates a new instance of the class without invoking any of the class's constructors. Consequently, any input validation checks 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 that has transient fields and any class or that performs validation checks in its constructors must also perform similar validation checks when being deserialized.
...
Code Block | ||
---|---|---|
| ||
public class NumberData extends Number { // ...implement abstract Number methods, like Number.doubleValue()... private static final NumberData INSTANCE = new NumberData (); public static NumberData getInstance() { return INSTANCE; } private NumberData() { // Perform security checks and parameter validation } protected int printData() { int data = 1000; // print data return data; } } class Malicious { public static void main(String[] args) { NumberData sc = (NumberData) deepCopy(NumberData.getInstance()); // Prints false; indicates new instance System.out.println(sc == NumberData.getInstance()); System.out.println("Balance = " + sc.printData()); } // This method should not be used in production code public 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); } } } |
...
This compliant solution uses composition over rather than extension of the Number
class. More information on singleton classes is available in rule MSC07-J. Prevent multiple instantiations of singleton objects.
...
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 ticket number of any lottery ticket to be 20,000. However, an attacker can manipulate the serialized array to generate a different number on deserialization.
Code Block | ||
---|---|---|
| ||
public class Lottery implements Serializable {
private int ticket = 1;
private 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();
}
}
|
...
Any input validation performed in the constructors must also be implemented at all places where ever an object can be deserialized. This compliant solution performs field-by-field validation by reading all fields of the object using the readFields()
method and ObjectInputStream.GetField
constructor. The value for each field must be fully validated before it is assigned to the object under construction. For more complicated invariants, this may require reading multiple field values into local variables to enable checks that depend on combinations of field values.
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 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 more information about finalizer attacks.) 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.
...
This compliant solution marks the fields as transient, so they are not serialized. The readObject()
method initializes them using the roll()
method. This class need not be final, as its fields are private and cannot be tampered with by subclasses.
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(); } } |
...
<ac:structured-macro ac:name="unmigrated-wiki-markup" ac:schema-version="1" ac:macro-id="387c28787e510d0e-e908707f-4bda4f51-b343be2d-a2281c3154cf82342298045f"><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="342ddd6500fc7d5a-d5c221d7-4c724138-ba4db32d-2ae20133dbd9362327416cf1"><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="8ee47152844c6f00-a4072fdb-439d4294-9520a24a-2692aca1f382e4f0abc11f49"><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="e6bf6d67a5dd2723-057949a3-47924276-828a83a5-8779116701399d980e301c3f"><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="f673d53c49b4f1ed-caaf3ad8-46e04630-b79ea940-473961f025419c5e04dd624b"><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> |
...