Java secure coding guide: serialization

brief introduction

Serialization is a very common and neglected function in Java. We need to serialize objects when they are written to files. At the same time, objects also need to be serialized if they want to be transmitted on the network.

The purpose of serialization is to ensure that objects can be transmitted correctly. What problems should we pay attention to in the process of serialization?

Let's have a look.

Introduction to serialization

If an object wants to realize serialization, it only needs to implement the serializable interface.

Strangely, serializable is an interface that does not require any implementation. If we implement serializable but do not override any methods, we will use the JDK's own serialization format.

However, if the class sending changes, such as adding fields, the default serialization format can not meet our needs. At this time, we need to consider using our own serialization method.

If the fields in the class do not want to be serialized, you can use the transient keyword.

Similarly, static represents class variables and does not need to be serialized.

Note the serialVersionUID

SerialVersionUID represents the sequence ID of the object. If we do not specify it, it is automatically generated by the JVM. In the process of deserialization, the JVM will first determine whether the serialVersionUID is consistent. If not, the JVM will think that it is not the same object.

If our instance needs to be modified later, be sure not to use the default serialVersionUID, otherwise the serialVersionUID will also change after the class is sent later, resulting in incompatibility with the previous serialization version.

Writeobject and readObject

If you want to implement serialization yourself, you can override the writeobject and readObject methods.

Note that these two methods are private and non static:

private void writeObject(final ObjectOutputStream stream)
    throws IOException {
  stream.defaultWriteObject();
}
 
private void readObject(final ObjectInputStream stream)
    throws IOException,ClassNotFoundException {
  stream.defaultReadObject();
}

If it is not private and non static, the JVM will not be able to discover these two methods and will not use them for custom serialization.

Readresolve and writereplace

If there are many fields in class, and these fields can be automatically generated from one of them, we don't need to serialize all the fields. We just serialize that field, and other fields can be derived from this field.

Readresolve and writereplace are proxy functions for serializing objects.

First, the serialized object needs to implement the writereplace method to replace it with the object you really want to write:

public class CustUserV3 implements java.io.Serializable{

    private String name;
    private String address;

    private Object writeReplace()
            throws java.io.ObjectStreamException
    {
        log.info("writeReplace {}",this);
        return new CustUserV3Proxy(this);
    }
}

Then, in the proxy object, you need to implement the readresolve method to reconstruct the serialized object from the serialized data. As follows:

public class CustUserV3Proxy implements java.io.Serializable{

    private String data;

    public CustUserV3Proxy(CustUserV3 custUserV3){
        data =custUserV3.getName()+ "," + custUserV3.getAddress();
    }

    private Object readResolve()
            throws java.io.ObjectStreamException
    {
        String[] pieces = data.split(",");
        CustUserV3 result = new CustUserV3(pieces[0],pieces[1]);
        log.info("readResolve {}",result);
        return result;
    }
}

Let's see how to use:

public void testCusUserV3() throws IOException,ClassNotFoundException {
        CustUserV3 custUserA=new CustUserV3("jack","www.flydean.com");

        try(FileOutputStream fileOutputStream = new FileOutputStream("target/custUser.ser")){
            ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
            objectOutputStream.writeObject(custUserA);
        }

        try(FileInputStream fileInputStream = new FileInputStream("target/custUser.ser")){
            ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
            CustUserV3 custUser1 = (CustUserV3) objectInputStream.readObject();
            log.info("{}",custUser1);
        }
    }

Note that we write and read custuserv3 objects.

Do not serialize inner classes

Inner classes are nested classes that are not explicitly or implicitly declared as static. Why don't we serialize inner classes?

Therefore, the following is correct:

public class OuterSer implements Serializable {
  private int rank;
  class InnerSer {
    protected String name;
  }
}

If you really want to serialize the inner class, set the inner class to static.

If there are custom variables in the class, do not use the default serialization

For serializable serialization, the constructor will not be executed during deserialization. Therefore, if we have certain constraints on the variables in the class in the constructor or other methods, these constraints must also be added in the process of deserialization, otherwise it will lead to malicious field ranges.

Let's give a few examples:

public class SingletonObject implements Serializable {
    private static final SingletonObject INSTANCE = new SingletonObject ();
    public static SingletonObject getInstance() {
        return INSTANCE;
    }
    private SingletonObject() {
    }

    public static 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);
        }
    }

    public static void main(String[] args) {
        SingletonObject singletonObject= (SingletonObject) deepCopy(SingletonObject.getInstance());
        System.out.println(singletonObject == SingletonObject.getInstance());
    }
}

The above is an example of a singleton object, in which we define a deepcopy method to copy the object through serialization, but the copied object is a new object. Although we define a singleton object, the final running result is false, which means that our system generates a different object.

How to solve this problem?

Add a readresolve method:

    protected final Object readResolve() throws NotSerializableException {
        return INSTANCE;
    }

In this readresolve method, we return instance to ensure that it is the same object.

Another case is that the fields in the class have ranges.

public class FieldRangeObject implements Serializable {

    private int age;

    public FieldRangeObject(int age){
        if(age < 0 || age > 100){
            throw new IllegalArgumentException("age范围不对");
        }
        this.age=age;
    }
}

What problems will the above class have in deserialization?

Because the above class does not verify the age field during deserialization, malicious code may generate out of range age data, which will overflow after deserialization.

How to deal with it?

Simply, we can judge the range in the readObject method:

    private  void readObject(java.io.ObjectInputStream s)
            throws IOException,ClassNotFoundException {
        ObjectInputStream.GetField fields = s.readFields();
        int age = fields.get("age",0);
        if (age > 100 || age < 0) {
            throw new InvalidObjectException("age范围不对!");
        }
        this.age = age;
    }

Do not call rewritable methods in readObject.

Why? ReadObject is actually a deserialized constructor. Before the readObject method ends, the object is not built or partially built. If readObject calls a rewritable method, malicious code can get the object that has not been fully instantiated in the method rewriting, which may cause problems.

Code for this article:

learn-java-base-9-to-20/tree/master/security

The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>