Understanding Java De-serialization
Hi All,
Welcome to my new blog on Java De-serialization. In this blog we will understand the basics of Java Deserilization, how is it vulnerable and how can this vulnerability be remediated. Here I will be discussing on the the category of JAVA deserialization which is human unreadable like binary data as opposed to other human readable java deserialization like JSON, XML, SOAP etc.
This is gonna be a long article so please bear with me.
So, What is Deserilization?
Well, deserilization is the process of converting stream of bytes into a copy of original object. Basically its the reverse process of Serialisation where we convert a object into byte stream.
Serialization and De-serialization concepts exists in almost all the languages like JAVA, .NET, python, nodejs etc.
Here in this blog we will be talking about serialization and de-serialization in JAVA and how it works.
Demo Time
So, lets start with some practicals to understand Java Serialization and Deserialization.
So, I have created a sample java class called employee which has 4 member variables name, address, ssn and phone number and a member function mailCheck as shown below:
public class Employee implements java.io.Serializable {public String name;public String address;public transient int SSN;public int number;public void mailCheck() {System.out.println(“Mailing a check to “ + name + “ “ + address);}}
Now, in java, if we need to make a class serializable we need to extend the it to serializable class using syntax “implements java.io.serializable” to class definition as is done above for the employee class.
Observe that SSN variable is assigned transient type.This is done to prevent that member variable from being serialized. So, if we don’t want any variable to get serialized we need to assign it to transient type.
Now that we have our sample class ready we need to create our serialization class and deserialization class. Below is the code for serialization class that I created:
import java.io.*;public class SerializeDemo {public static void main(String [] args) {Employee e = new Employee();e.name = "Reyan Ali";e.address = "Phokka Kuan, Ambehta Peer";e.SSN = 11122333;e.number = 101;try {FileOutputStream fileOut =new FileOutputStream("/tmp/employee.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(e); //Serialization done hereout.close();fileOut.close();System.out.printf("Serialized data is saved in /tmp/employee.ser");} catch (IOException i) {i.printStackTrace();}}}
Here, we are creating an object of the employee class and assigning values to the member variables of the class and then serializing the object.
As can be seen from above code, we are creating a file called employee.ser and copying the serialized data to it.
The actual serialization is done when the function “writeObject” gets called as shown in above code. Once the code gets executed, the serialized data gets stored in employee.ser
The below screenshot shows execution of the code when:
Now lets have a look at the employee.ser file content:
It can be observed from above screenshot that using linux utility “file” gives the output as “Java serialization data”.
Now looking at the hex code of the file using command “xxd employee.ser” provides us with data as shown above.
It can be observed that java serialized data always begins with hex pattern “aced” or base64 pattern “rO0". Hence, during a penetration testing if we observe either of these pattern going as part of HTTP Request then it can be concluded that its java serialized input.
Now, that we are done with the serialization we need to move to understanding the deserialization process. Below is the code for the deserialization of the serialized data:
import java.io.*;import java.net.URL;import java.net.URLClassLoader;
public class DeserializeDemo {public static void main(String [] args) {Employee e = null;try {FileInputStream fileIn = new FileInputStream("/tmp/employee.ser");ObjectInputStream in = new ObjectInputStream(fileIn);e = (Employee) in.readObject(); //Deserialization done herein.close();fileIn.close();} catch (IOException i) {i.printStackTrace();return;} catch (ClassNotFoundException c) {System.out.println("Employee class not found");c.printStackTrace();return;}e.mailCheck();System.out.println("Deserialized Employee...");System.out.println("Name: " + e.name);System.out.println("Address: " + e.address);System.out.println("SSN: " + e.SSN);System.out.println("Number: " + e.number);}}
In the above code, the employee.ser, having serialized content, was used to deserialize to employee object and retrieve data. In the above code, the line:
e = (Employee) in.readObject();
is the one where all the deserialization happens. However, the deserialized data needs to be type-casted to a java class type which in this case is “Employee”. Once the data is type-casted we would be able to retrieve the data as shown below:
PS:One important thing to note here is that SSN value is 0 in the output even though it was assigned a value when serializing the employee object. This is because it was assigned to transient type when declaring the member variables of employee class due to which it does not become part of the serialization.
Now, that we have a clear picture of what serialization and deserialization is, we need to understand where the problem lies.
So, the main problem lies with the way readObject deserializes data which means even though we provide a serialized input of different class, it gives an error but still executes the class and now if that class has its own readObject implementation then that also gets executed before giving an error.
Let me demonstrate this with very simple example. Lets create another class with custom readObject implementation and make the class serializable:
import java.io.IOException;public class ExploitDeser implements java.io.Serializable{private void readObject(java.io.ObjectInputStream in) throws IOException, ClassNotFoundException{in.defaultReadObject();Runtime.getRuntime().exec("/Applications/Calculator.app/Contents/MacOS/Calculator");}}
Observe the custom implementation of readObject in the above class which which will execute “Runtime.getRuntime().exec” and opens up the calculator.
Now lets serialize the object of the class and insert serialized content to a file:
import java.io.*;public class SerializeDemo {public static void main(String [] args) {ExploitDeser e=new ExploitDeser();try {FileOutputStream fileOut =new FileOutputStream("/tmp/malicious.ser");ObjectOutputStream out = new ObjectOutputStream(fileOut);out.writeObject(e);out.close();fileOut.close();System.out.printf("Serialized data is saved in /tmp/malicious.ser");} catch (IOException i) {i.printStackTrace();}}}
After execution of the above code serialized content gets saved to a file called malicious.ser
Now, we give the path of this newly created file as input for deserialization in our previous deserialization code without making the type-casting changes as shown below:
Observe that even though we provided serialized data for a different class(ExploitDeser), the type-casting done for readObject still remains the same (type-casted to Employee class).
Now if we execute this code we see an error in the console however, we can observe that the calculator is getting popped up.
As seen above, the code gives an error “Exception in thread “main” java.lang.ClassCastException: testserialization.ExploitDeser cannot be cast to testserialization.Employee” which means the type-casting is not proper however the code still gets executed. The readObject function of the DeserializeDemo class is able to execute the custom implementation of the readObject of ExploitDeser class.
This is what is the the insecure deserialization here, that even though there is a type-casting error still if serialized data of a custom class with a custom implementation of readObject is provided as input, it gets executed as shown above.
This is what is done by the gadget chains used by ysoserial and other java deserialization exploitation tools where they look for chaining of classes to finally land into a custom implementation of readObject which can execute system level code provided as part of user input.
Remediation
Now, that we have had a look at what is Deserialization and how it gets exploited, lets look at the remediation to this.
The remediation we are going to discuss here is called “Look-ahead class validation” where in we can whitelist the list of classes that we want to get deserialized. For example. in our scenario we would expect to allow only Employee class object data to get deserialized and for remaining it should not allow deserialization.
So, basically what this method does is, it overrides the function “resolveClass” which is a part of “ObjectInputStream”(used during deserialization) class and performs a white listing of the list of classess that needs to be deserialized.
Now that we are done with the theory, lets jump in to the implementation.
Lets create a new class “LookAheadObjectInputStream” extend it to ObjectInputSteam class so that we can override the member function resolveClass:
class LookAheadObjectInputStream extends ObjectInputStream {public LookAheadObjectInputStream(InputStream inputStream)throws IOException {super(inputStream);}@Overrideprotected Class<?> resolveClass(ObjectStreamClass desc) throws IOException,ClassNotFoundException {if (!desc.getName().equals(Employee.class.getName())) {throw new InvalidClassException("Unauthorized deserialization attempt",desc.getName());}return super.resolveClass(desc);}}
In the above code the line:
if (!desc.getName().equals(Employee.class.getName())) {
is basically looking if serialized content contains object of Employee class else it provides error “Unauthorized deserialization attempt” and prevents its execution.
Now, the final modified code for “DeserializedDemo” is as shown below:
In the above code, observe the highlighted code in which instead of calling the ObjectInputStream class, LookAheadObjectInputStream class is getting called which will override the resolveClass method during runtime and execute accordingly.
Now, lets pass the malicious.ser file created previously as input for deserialization and observe the output:
As can be seen above, this time the custom implementation of readObject from the custom class “ExploitDeser” does not get executed during deserialization. Instead we are provided with the custom error message “Unauthorized deserialization attempt” which was set in the overridden resolveClass function under the LookAheadObjectInputStream class.
This is a very simple example of using look-ahead class validation method to prevent insecure deserialization. An extensive implementation around this idea is the “serialKiller” library. Link can be found below:
https://github.com/ikkisoft/SerialKiller
Hence, this can be considered as one of the solutions to mitigate insecure deserialization in java.
I am still looking into other methods of mitigations which I will be blogging about later.
Please provide your valuable feedback. Till then keep hacking and learning….