Java serialization allows developers to save a Java object as a binary format, so that the object can be persisted into a file or transmitted over the network. Remote method call (RMI) uses serialization as the communication medium between the client and the server. When the Service receives binary data from the client and deserializes the input data to construct a Java instance, multiple security problems will occur. This article focuses on one of the following problems: hackers may serialize another class instance and pass it to the service program. The service program will serialize the malicious object and forcibly convert the object to the valid type expected by the service. This will cause exceptions. However, this exception is too late to ensure data security. This article explains why and how to implement a secure serialization. Fragile class your service program cannot deserialize any class object. Why not? The simple answer is: there may be vulnerable classes exploited by hackers in the server's class path. The Code contained in these classes creates DOS conditions for hackers, or -- in extreme cases -- allows hackers to inject arbitrary code. You may believe that there is a possibility of such attacks, but considering that there are too many classes in the class path of a typical server program, not only your own code, but also the Java core class library, third-party class libraries and other class libraries in the middleware or framework. In addition, in the application life cycle, the class path may be changed, or in order to cope with changes in the underlying runtime environment, the class path of the application may also be modified. When attempting to exploit this weakness, hackers can combine these operations by transmitting multiple serialized objects. I should stress that the service will serialize a malicious object only when the following conditions are met: 1. the class of the malicious object exists in the class path of the server. It is impossible for a hacker to transmit serialized objects of any class at will, because the application service may not be able to load this class. 2. The classes of malicious objects are either serializable or externalized. (That is, this class on the server must implement java. io. serializable or java. io. externalizable) in addition, by directly copying data from the serialized stream, the object tree can be generated by the deserialization operation without calling the constructor, therefore, hackers cannot execute Java code in the constructor of the serialized object class. However, hackers have other ways to execute code on the server. No matter when the JVM deserializes an object, it will implement one of the following three methods and call and execute the code in this method: 1. method readObject (). This method is generally used by developers when the standard serialization mechanism is not applicable. For example, if you need to assign values to transient member variables. 2. The readResolve () method is generally used to serialize a singleton object. 3. Method readExternal (), used for external objects. Therefore, if there is a class using the above method in your class path, you must realize that hackers may remotely call these methods. In the past, such attacks were used to damage the Applet Security Sandbox. Likewise, the same attack technology can also be used for server-side applications. If you continue reading the code, you will see how to allow the application service to serialize only the objects of the expected class. After an object is serialized in Java serialized binary format, binary data will contain metadata (information related to the data structure, such as the name of the class, number of members, and the object data itself. I will use a simple Bicycle class as an example. As shown in Listing 1, this class contains three member variables (id, name, and nbrWheels) and the corresponding set and get methods. Listing 1. bicycle class package com. ibm. ba. scg. lookAheadDeserializer; public class Bicycle implements java. io. serializable {private static final long serialVersionUID = 5754109541168320730l; private int id; private String name; private int nbrWheels; public Bicycle (int id, String name, int nbrWheels) {this. id = id; this. name = name; this. nbrWheels = nbrWheels;} public String getName () {return name;} publ Ic void setName (String name) {this. name = name;} public void setId (int id) {this. id = id;} public int getId () {return id;} public int getNbrWheels () {return nbrWheels;} public void setNbrWheels (int nbrWheels) {this. nbrWheels = nbrWheels;} after an instance of the class shown in Listing 1 is serialized, its data flow is shown in Listing 2. serialization stream of the Bicycle class 000000: ac ed 00 05 73 72 00 2C 63 6F 6D 2E 69 62 6D 2E | · com. ibm. | 000016: 62 61 2E 73 63 67 2E 4C 6F 6F 6B 41 68 65 61 64 | ba. scg. lookAhead | 000032: 44 65 73 65 72 69 61 6C 69 7A 65 72 2E 42 69 63 | Deserializer. bic | 000048: 79 63 6C 65 4F da af 97 F8 CC C0 DA 02 00 03 49 | ycle · I | 000064: 00 02 69 64 49 00 09 6E 62 72 57 68 65 65 6C 73 | · idI · nbrWheels | 000080: 4C 00 04 6E 61 6D 65 74 00 12 4C 6A 61 76 61 2F | L · name · Ljava/| 000096: 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 78 70 00 00 | l Ang/String; · | 000112: 00 00 00 00 00 01 74 00 08 55 6E 69 63 79 63 6C |... Unicycl | 000128: 65 | e | apply the standard Object serialization stream Protocol to the above data. You will see the serialization object shown in listing 3: Listing 3. details of the serialized Bicycle object STREAM_MAGIC (2 bytes) 0 xACED STREAM_VERSION (2 bytes) 5 newObject TC_OBJECT (1 byte) 0x73 newClassDesc TC_CLASSDESC (1 byte) 0x72 className length (2 bytes) 0x2C = 44 text (59 bytes) com. ibm. ba. scg. lookAheadDeserializer. bicycle serialVer SionUID (8 bytes) bytes = 5754104541168320730 classDescInfo classDescFlags (1 byte) 0x02 = SC _SERIALIZABLE fields count (2 bytes) 3 field [0] primitiveDesc prim_typecode (1 byte) I = integer fieldName length (2 bytes) 2 text (2 bytes) id field [1] primitiveDesc prim_typecode (1 byte) I = integer fieldName length (2 bytes) 9 text (9 bytes) nbrWheels field [2] objectDesc obj_typecode (1 Te) L = object fieldName length (2 bytes) 4 text (4 bytes) name className1 TC_STRING (1 byte) 0x74 length (2 bytes) 0x12 = 18 text (18 bytes) Ljava/lang/String; classAnnotation TC_ENDBLOCKDATA (1 byte) 0x78 superClassDesc TC_NULL (1 byte) 0x70 classdata [] classdata [0] (4 bytes) 0 = id classdata [1] (4 bytes) 1 = nbrWheels classdata [2] TC_STRING (1 byte) 0x74 length (2 bytes) 8 text (8 bytes) Unicyc From listing 3, you can see that the serialization object type is com. ibm. ba. scg. lookAheadDeserializer. the ID of a Bicycle is 0 and there is only one wheel, that is, it is a Bicycle. The point is that the binary format contains a file header, which allows you to verify the input. As you can see in listing 3, when reading the binary stream, you will first see the type description of the serialized object before it appears. This structure allows the implementation of its own algorithm to read the type description, and depends on the class name to determine whether to continue reading the serialized stream. Fortunately, by using a "Hook" provided by Java that is commonly used for custom class loading, you can easily implement this function-that is, override the resolveClass () method. This "Hook" method is very suitable for providing custom validation functions. You can use this method to throw an exception whenever a serialized stream contains an unexpected class. You need to inherit the java. io. ObjectInputStream class and overwrite the resolveClass () method. The code in Listing 4 uses this technology to ensure that only instances of the Bicycle class can be deserialized. Listing 4. customize the "Hook" Program package com. ibm. ba. scg. lookAheadDeserializer; import java. io. IOException; import java. io. inputStream; import java. io. invalidClassException; import java. io. objectInputStream; import java. io. objectStreamClass; import com. ibm. ba. scg. lookAheadDeserializer. bicycle; public class LookAheadObjectInputStream extends ObjectInputStream {public LookAheadObjectInputStream (InputStream inputStream) th Rows IOException {super (inputStream);}/*** Only deserialize instances of our expected Bicycle class */@ Override protected Class <?> ResolveClass (ObjectStreamClass desc) throws IOException, ClassNotFoundException {if (! Desc. getName (). equals (Bicycle. class. getName () {throw new InvalidClassException ("Unauthorized deserialization attempt", desc. getName ();} return super. resolveClass (desc. ibm. ba. scg. the instance of the LookAheadDeserializer class calls the readObject () method to prevent the deserialization of the expected object. As a sample application, listing 5 serializes two objects-one is the expected class (com. ibm. ba. scg. lookAheadDeserializer. the other is the class (java. io. file), and then use the custom validation "Hook" program in Listing 4 to try them for deserialization. Listing 5. use the customized "Hook" Program package com. ibm. ba. scg. lookAheadDeserializer; import java. io. byteArrayInputStream; import java. io. byteArrayOutputStream; import java. io. file; import java. io. IOException; import java. io. objectInputStream; import java. io. objectOutputStream; import com. ibm. ba. scg. lookAheadDeserializer. bicycle; public class LookAheadDeserializer {private static byte [] serialize (Object obj) throws IOExceptio N {ByteArrayOutputStream baos = new ByteArrayOutputStream (); ObjectOutputStream oos = new ObjectOutputStream (baos); oos. writeObject (obj); byte [] buffer = baos. toByteArray (); oos. close (); baos. close (); return buffer;} private static Object deserialize (byte [] buffer) throws IOException, ClassNotFoundException {ByteArrayInputStream bais = new ByteArrayInputStream (buffer); // We use LookAheadObject InputStream instead of InputStream ObjectInputStream ois = new LookAheadObjectInputStream (bais); Object obj = ois. readObject (); ois. close (); bais. close (); return obj;} public static void main (String [] args) {try {www.2cto.com // Serialize a Bicycle instance byte [] serializedBicycle = serialize (new Bicycle (0, "Unicycle", 1); // Serialize a File instance byte [] serializedFile = serialize (new Fil E ("Pierre Ernst"); // Deserialize the Bicycle instance (legitimate use case) Bicycle bicycle0 = (Bicycle) deserialize (serializedBicycle); System. out. println (bicycle0.getName () + "has been deserialized. "); // Deserialize the File instance (error case) Bicycle bicycle1 = (Bicycle) deserialize (serializedFile);} catch (Exception ex) {ex. printStackTrace (System. err) ;}} when running the application. io. file Before the object is deserialized, JVM throws an exception, as shown in figure 1. application output conclusions this article shows you how to use encryption, signatures, simple member variable verification, and other methods after discovering undesirable classes in the serialized stream, how to stop Java deserialization as soon as possible. Remember that the entire object tree (root object, and all member objects) is constructed during the deserialization process. In more complex cases, you may have to allow more classes to be deserialized.