5.3.7. Deserialization¶
5.3.7.1. Introduction¶
Serialization is to convert objects into byte streams, which are convenient for saving in memory, files, and databases; deserialization is the reverse process, which is restored from byte streams to objects. It is generally used in scenarios such as remote invocation, transferring objects to a remote server over a network, storing objects in a database, or waiting for reuse locally. ObjectOutputStream
Class methods in Java writeObject()
can implement serialization, and ObjectInputStream
class readObject()
methods are used for deserialization. If you want to implement deserialization of a class, you implement the Serializable
interface .
A deserialization vulnerability exists when a remote service accepts untrusted data and deserializes it and an exploitable class exists in the current environment.
5.3.7.1.1. Sequence data structures¶
0xaced
Magic Head / STREAM_MAGIC0x0005
version number / STREAM_VERSION / refer to java.io.ObjectStreamConstant0x73
Object Type Identifier0x72
class descriptor identifier
5.3.7.1.2. Serialization Process¶
When the ObjectOutputStream instance is initialized, the magic header and version number are written to bout (BlockDataOutputStream type)
- Call ObjectOutputStream.writeObject() to start writing object data
ObjectStreamClass.lookup() encapsulates the class description to be serialized (returns ObjectStreamClass type), obtains the class name, custom serialVersionUID, serializable fields (returns ObjectStreamField type) and constructor, as well as writeObject, readObject methods, etc.
- writeOrdinaryObject() writes object data
write object type identifier
- writeClassDesc()enters branch writeNonProxyDesc() to write class description data
write class descriptor identifier
write class name
write SUID (when SUID is empty, it will be calculated and assigned)
calculate and write serialized attribute flags
write field info data
write Block Data end flag
write parent class description data
- writeSerialData()writes the serialized data of the object
If the class customizes writeObject(), call this method to write the object, otherwise call defaultWriteFields() to write the field data of the object (if it is a non-primitive type, recursively process the sub-object)
5.3.7.1.3. Deserialization Process¶
When the ObjectInputStream instance is initialized, read the magic header and version number for verification
- Call ObjectInputStream.readObject() to start reading object data
Read Object Type ID
- readOrdinaryObject() reads the data object
- readClassDesc() reads class description data
Read the class descriptor identifier and enter the branch readNonProxyDesc()
read class name
read SUID
Read and parse serialized attribute flags
Read field information data
resolveClass() gets the Class object of the class to be deserialized according to the class name, and throws ClassNotFoundException if the acquisition fails
skipCustomData() loops to read bytes until the end of Block Data is marked
Read parent class description data
In initNonProxy(), it is judged whether the SUID and class name (excluding the package name) of the object and the local object are the same. If they are different, an InvalidClassException will be thrown
ObjectStreamClass.newInstance() gets and calls the no-argument constructor of the nearest non-Serializable parent class to the object (if it does not exist, returns null) to create an object instance
- readSerialData()reads the serialized data of an object
If the class defines readObject(), call this method to read the object, otherwise call defaultReadFields() to read and fill the field data of the object
5.3.7.2. Vulnerability Exploitation¶
5.3.7.2.1. Dangerous base libraries¶
com.mchange:c3p0 0.9.5.2
com.mchange:mchange-commons-java 0.2.11
commons-beanutils 1.9.2
commons-collections 3.1
commons-fileupload 1.3.1
commons-io 2.4
commons-logging 1.2
org.apache.commons:commons-collections 4.0
org.beanshell:bsh 2.0b5
org.codehaus.groovy:groovy 2.3.9
org.slf4j:slf4j-api 1.7.21
org.springframework:spring-aop 4.1.4.RELEASE
5.3.7.2.2. Echo Mode¶
Echo via middleware feature
echo by throwing an exception
echo via OOB
echo by writing static files
5.3.7.3. Vulnerability fixes and protection¶
5.3.7.3.1. Hook resolveClass¶
When readObject()
using deserialization, the resolveClass
method will be called to read the deserialized class name. You can check the deserialized class by hooking this method. A demo is as follows:
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
if (!desc.getName().equals(SerialObject.class.getName())) {
throw new InvalidClassException(
"Unauthorized deserialization attempt",
desc.getName());
}
return super.resolveClass(desc);
}
The above demo only allows serialization. SerialObject
In this way, you can set a whitelist that allows serialization to prevent deserialization vulnerabilities from being exploited. SerialKiller/Jackson/Weblogic etc all use this method to defend.
5.3.7.3.2. ValidatingObjectInputStream¶
Apache Commons IO Serialization ValidatingObjectInputStream
class provides a accept
method through which the deserialization class white/blacklist control can be implemented. A demo is as follows:
private static Object deserialize(byte[] buffer) throws IOException, ClassNotFoundException , ConfigurationException {
Object obj;
ByteArrayInputStream bais = new ByteArrayInputStream(buffer);
ValidatingObjectInputStream ois = new ValidatingObjectInputStream(bais);
ois.accept(SerialObject.class);
obj = ois.readObject();
return obj;
}
5.3.7.3.3. ObjectInputFilter(JEP290)¶
Java 9 provides new features that support serialized data filtering. You can inherit the java.io.ObjectInputFilter
class override checkInput
method to implement custom filters, and use the ObjectInputStream
object ‘s setObjectInputFilter
set filter to implement deserialization class white/blacklist control. This mechanism itself is a new feature for Java 9, but then the official suddenly decided to introduce the enhancement mechanism downwards, supporting JDK 6, 7, and 8 respectively. This mechanism mainly describes the following mechanisms:
Provides a mechanism to restrict deserialization classes, whitelist or blacklist
Limit the depth and complexity of deserialization
Provides a mechanism for validating classes for RMI remote invocation objects
Define a configurable filtering mechanism, for example, you can define a filter in the form of a configuration properties file