Contents | Previous | Next | JavaTM Object Serialization Specification |
The object serialization system allows a bytestream to be produced from a graph of objects, sent out of the Java™ environment (either saved to disk or transmitted over the network) and then used to recreate an equivalent set of new objects with the same state.
What happens to the state of the objects outside of the environment is outside of the control of the Java™ system (by definition), and therefore is outside the control of the security provided by the system. The question then arises: once an object has been serialized, can the resulting byte array be examined and changed in a way that compromises the security of the Java program that deserializes it? The intent of this section is to address these security concerns.
The goal for object serialization is to be as simple as possible and yet still be consistent with known security restrictions; the simpler the system is, the more likely it is to be secure. The following points summarize the security measures present in object serialization:
java.io.Serializable
or java.io.Externalizable
interfaces can be serialized. Mechanisms are provided which can be used to prevent the serialization of specific fields (typically, those containing sensitive or unneeded data).Naive use of object serialization may allow a malicious party with access to the serialization byte stream to read private data, create objects with illegal or dangerous state, or obtain references to the private fields of deserialized objects. Implementors concerned with security should be aware of the following implications of serialization:
readExternal
method at any time, passing it an arbitrary stream to read values from, causing the target object to be reinitialized. A means of preventing this is outlined in Section A.7 "Preventing Overwriting of Externalizable Objects”.Fields containing sensitive data should not be serialized; doing so exposes their values to any party with access to the serialization stream. There are several methods for preventing a field from being serialized:
serialPersistentFields
field of the class in question, and omit the field from the list of field descriptors.writeObject
or writeExternal
) which does not write the field to the serialization stream (i.e., by not calling ObjectOutputStream.defaultWriteObject
).To guarantee that a deserialized object does not have state which violates some set of invariants that need to be guaranteed, a class can define its own serializing and deserializing methods. If there is some set of invariants that need to be maintained between the data members of a class, only the class can know about these invariants, and it is up to the class author to provide a deserialization method that checks these invariants.
Security-conscious implementors should keep in mind that a serializable class’ readObject
method is effectively a public constructor, and should be treated as such. This is true whether the readObject
method is implicit or explicit. It is not safe to assume that the byte stream that is provided to the readObject
method was generated by serializing a properly constructed object of the correct type. It is good defensive programming to assume that the byte stream is provided by an adversary whose goal is to compromise the object under construction.
This is important even if you are not worried about security; it is possible that disk files can be corrupted and serialized data be invalid. So checking such invariants is more than just a security measure; it is a validity measure. However, the only place it can be done is in the code for the particular class, since there is no way the serialization package can determine what invariants should be maintained or checked.
In version 1.4 of the Java™ 2 SDK, Standard Edition, support was added for class-defined readObjectNoData
methods (see Section 1.5 "The readObjectNoData Method”). Non-final
serializable classes which initialize fields to non-default values should define a readObjectNoData
method to ensure consistent state in the event that a subclass instance is deserialized and the serialization stream does not list the class in question as a superclass of the deserialized object. This may occur in cases where the receiving party uses a different version of the deserialized instance’s class than the sending party, and the receiver’s version extends classes that are not extended by the sender’s version. This may also occur if the serialization stream has been tampered; hence, readObjectNoData
is useful for initializing deserialized objects properly despite a “hostile” or incomplete source stream
If a class has any private or package private object reference fields, and the class depends on the fact that these object references are not available outside the class (or package), then either the referenced objects must be defensively copied as part of the deserialization process, or else the ObjectOutputStream.writeUnshared
and ObjectInputStream.readUnshared
methods (introduced in version 1.4 of the Java™ 2 SDK, Standard Edition) should be used to ensure unique references to the internal objects.
In the copying approach, the sub-objects deserialized from the stream should be treated as "untrusted input": newly created objects, initialized to have the same value as the deserialized sub-objects, should be substituted for the sub-objects by the readObject
method. For example, suppose an object has a private byte array field, b, that must remain private:
private void readObject(ObjectInputStream s) throws IOException, ClassNotFoundException { s.defaultReadObject(); b = (byte[])b.clone(); if (<invariants are not satisfied>) throw new java.io.StreamCorruptedException(); }
This issue is particularly important when considering serialization of immutable objects containing internal (necessarily private) references to mutable sub-objects. If no special measures are taken to copy the sub-objects during deserialization of the container object, then a malicious party with write access to the serialization stream may violate the container object’s immutability by forging references to its mutable sub-objects, and using these references to change the internal state of the container object. Thus, in this case it is imperative that the immutable container class provide a class-specific deserialization method which makes private copies of each mutable component object it deserializes. Note that for the purpose of maintaining immutability, it is unnecessary to copy immutable component objects.
It is also important to note that calling clone
may not always be the right way to defensively copy a sub-object. If the clone
method cannot be counted on to produce an independent copy (and not to "steal" a reference to the copy), an alternative means should be used to produce the copy. An alternative means of copying should always be used if the class of the sub-object is not final, since the clone
method or helper methods that it calls may be overridden by subclasses.
Starting in version 1.4 of the Java™ 2 SDK, Standard Edition, unique references to deserialized objects can also be ensured by using the ObjectOutputStream.writeUnshared
and ObjectInputStream.readUnshared
methods, thus avoiding the complication, performance costs and memory overhead of defensive copying. The readUnshared
and writeUnshared
methods are further described in Section 1.1 "The ObjectInputStream Class” and Section 1.1 "The ObjectOutputStream Class”.
Objects which implement the Externalizable
interface must provide a public readExternal
method. Since this method is public, it can be called at arbitrary times by anyone with access to the object. To prevent overwriting of the object’s internal state by multiple (illegal) calls to readExternal
, implementors may choose to add checks to insure that internal values are only set when appropriate:
public synchronized void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { if (! initialized) { initialized = true; // read in and set field values ... } else { throw new IllegalStateException(); } }
Another way of protecting a bytestream outside the virtual machine is to encrypt the stream produced by the serialization package. Encrypting the bytestream prevents the decoding and the reading of a serialized object’s private state, and can help safeguard against tampering with stream contents.
Object serialization allows encryption, both by allowing classes to define their own methods for serialization and deserialization (inside which encryption can be used), and by adhering to the composable stream abstraction (allowing the output of a serialization stream to be channelled into another filter stream which encrypts the data).
Contents | Previous | Next | JavaTM Object Serialization Specification |
Copyright © 2003 Sun Microsystems, Inc. All rights reserved.