A Talk About Java Serialization and Deserialization - Tutorial Boy -->

A Talk About Java Serialization and Deserialization




 

Preface

The current popularity of Java security can be said to be a must-know for the red team. I once fell into the beginning of learning Java security - learning CC chain - giving up - starting to learn Java again The vicious circle of safety is like memorizing words and always stops at abandon. Finally, after seeing a blog of senior scz, I made up my mind.

Basic Use

The concepts of serialization and deserialization are no longer introduced, let's look at the user directly.

For a class to be serializable, it needs to implement the Serializable and Externalizable interfaces, the former being an empty interface:

public interface Serializable {
}
It is only used to identify that this class can be serialized, the latter inheriting the former.

The serialization steps of a Java object:
  • Create one  that can wrap an output stream of another typejava.io.ObjectOutputStream
  • writeObjectwrite an object through its  methods

Steps to deserialize a Java object:
  • Create one that can wrap an input stream of another type java.io.ObjectInputStream
  • readObject Read an object through its  methods

Serialized Data Format

Write the serialized data to a file and observe:

You can see that there are some readable strings, including the class name and some member variable names and values.

Using the tool: SerializationDumper serialized data can be easily restored, such as for the original flow file:

For hexadecimal data:

Complete input:

In contrast to the Object Serialization Stream Protocol [2] section in Oracle's official documentation, let's discuss the structure of the following serialized data.

The first place is the Magic Number and the protocol version. You can see the definition in the ObjectStreamConstantsinterface :

followed by contents, i.e., one or more content, the latter consisting of objector block data.

objectThe content inside is the core of serialized data and consists of any of the following:
  • newObject: object
  • newClass: kind
  • newArray: Array
  • newString: String
  • newEnum: enumeration type
  • newClassDesc: class definition
  • prevObject: a reference to a type
  • nullReference:null
  • exception:abnormal
  • TC_RESET: reset ReferenceID
See new object the :

We use the above example to compare, Contents which contains one newObject, TC_OBJECTafter is classDesc, which contains the class name and length, serialVersionUID, attribute name and length, parent class, and other information. After this is newHandle and, the former is the unique ID of the current structure in the serialized data, and the latter is the information in the serialized object.classdata[]

newClassDescand classDesc are not the same, as can be seen from the definition:

classDescEquivalent to newClassDescthe encapsulation of, which can newClassDescbe either a null or a pointer to a class definition.

At this point, you may be wondering, why should I read this boring document? Why didn't you start talking about the CC chain?

There are three reasons why you need to learn more about serialized data structures:
  • Helps to understand the principle of bypassing WAF by filling garbage characters in serialized data
  • Helps to learn about JDK 8u20 native deserialization vulnerabilities later
  • Maybe you will run into situations where you need to use other languages ​​to do Java deserialization exploits

The Effect of The Attribute

When serializing in PHP, the scope of variables will affect the serialized data, so is there a similar situation in Java?

Add two variables to the Personclass :

After observing the serialized data, it is found that neither of these two variables exists:

Variables modified by the static or transient keyword will not appear in the serialized data, this is for some sensitive data considerations.

But if you try to call these two variables after deserialization, you can see addressnormal output, while password is null:
This is because addresses a static variable, and its value registered in the JVM is called, not the value obtained after serialization.

If you want to serialize variables modified by the transient keyword , you need to use the Externalizableinterface:


Here, if used to parse, the following error will occur:test.rawSerializationDumper


The reason is that a class that implements the Externalizable interface , whose serialization is writeExternalwritten to the stream through the method, must also be parsed through the corresponding readExternalmethod, so SerializationDumpersuch serialized data cannot be parsed without providing the original class.

ObjectStreamClass analysis

ObjectStreamClass It can be used to analyze the serialization characteristics of serialized classes loaded in the JVM, including field description information and serialVersionUIDetc.

ObjectStreamClassThere are two static methods:

lookup(Class<?>cl) ObjectStreamClass An instance is returned if the provided class is serializable , otherwise null:

The method will return the corresponding instance regardless of whether the provided class is deserializable or not. lookup any(Class<?>cl)

After getting the ObjectStreamClass instance, you can call the corresponding method to get the information:
  • getDeclaredSUID: Extract the serial number
  • getSerialFields: Extract the required serialization fields, if not, extract the default fields

About ObjectInputStream.resolveClass()



image.png
resolveClass The method receives an ObjectStreamClass instance, obtains its class name, and then uses reflection to return a Class instance of this class. In fact, it is allowed to replace or parse the object before returning the object during deserialization.

This method is overridden in Apache Shiro:


This leads to some interesting situations when Shiro deserializes exploits, which will be discussed in detail in a later Shiro article.

Overriding this method is also a means of defending against deserialization vulnerabilities, such as the SerialKiller [3] project:

Blacklist or whitelist defense is performed by rewriting .ObjectInputStream.resolveClass()

readObjectNoData

During deserialization, if the superclass of the serialized class is different from the superclass of the deserialized class because the class at the time of serialization is different from the version at the time of deserialization, or because the received serialized data is incomplete, or Serialized data is harmful, and will affect the initialized object field values.

So a serializable class should define its own readObjectNoData method, which will be used readObjectNoData instead in the above case readObject. Without this method, the fields of the class are initialized to their default values.

For example, serialize using the current class:

Update this class and use the previously serialized data for deserialization:

will be called instead . readObjectNoData()  readObject()

Summary

This is the first article in "Java Security Guide ". Some places are actually a bit odd, and it is not necessarily helpful for Java security learning, but it is always right to know more, right :)

References