Contents | Prev | Next Java Object Serialization Specification
version 1.5.0


Security in Object Serialization

Chapter A

Topics:

  • Overview
  • Design Goals
  • Security Issues
  • Preventing Serialization of Sensitive Data
  • Writing Class-Specific Serializing Methods
  • Guarding Unshared Deserialized Objects
  • Preventing Overwriting of Externalizable Objects
  • Encrypting a Bytestream

    A.1 Overview

    The object serialization system allows a bytestream to be produced from a graph of objects, sent out of the JavaTM 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 JavaTM 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.

    A.2 Design Goals

    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:

  • Only objects implementing the 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).
  • The serialization package cannot be used to recreate or reinitialize objects. Deserializing a byte stream may result in the creation of new objects, but will not overwrite or modify the contents of existing objects.
  • Although deserializing an object may trigger downloading of code from a remote source, the downloaded code is restricted by all of the usual JavaTM code verification and security mechanisms. Classes loaded as a side-effect of deserialization are no more or less secure than those loaded in any other fashion.

    A.3 Security Issues

    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:

  • Default serialization of an object writes the values of all fields of that object to the serialization stream, regardless of whether or not they are public. Malicious code can effectively read the values of private fields of a serializable object by serializing the object and then examining the resulting byte stream. Methods for avoiding this problem are described in SectionA.4 Preventing Serialization of Sensitive Data".
  • During deserialization, objects are created and initialized using data from the incoming serialization stream. If the stream was corrupted or tampered with prior to deserialization, the deserialized objects may have unexpected or illegal state. Methods for avoiding this problem are described in SectionA.5 Writing Class-Specific Serializing Methods".
  • By inserting extra wire handle references into a serialization byte stream, it is possible during deserialization to forge extra references to objects occurring earlier in the stream. Therefore, it is unsafe for developers to assume that references to private objects obtained via deserialization are unique. Techniques for dealing with this problem are discussed in section SectionA.6 Guarding Unshared Deserialized Objects".
  • Objects implementing the Externalizable interface are susceptible to overwriting, since the readExternal method is public. A caller can invoke the 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 SectionA.7 Preventing Overwriting of Externalizable Objects".

    A.4 Preventing Serialization of Sensitive Data

    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:

  • Declare the field as private transient.
  • Define the serialPersistentFields field of the class in question, and omit the field from the list of field descriptors.
  • Write a class-specific serialization method (i.e., writeObject or writeExternal) which does not write the field to the serialization stream (i.e., by not calling ObjectOutputStream.defaultWriteObject).

    A.5 Writing Class-Specific Serializing Methods

    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 JavaTM 2 SDK, Standard Edition, support was added for class-defined readObjectNoData methods (see Section3.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

    A.6 Guarding Unshared Deserialized Objects

    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 JavaTM 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 JavaTM 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 Section3.1 The ObjectInputStream Class" and Section2.1 The ObjectOutputStream Class".

    A.7 Preventing Overwriting of Externalizable Objects

    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();
            }
        }
    
    

    A.8 Encrypting a Bytestream

    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 | Prev | Next Java Object Serialization Specification
    version 1.5.0

    Copyright © 2004 Sun Microsystems, Inc. All rights reserved