Insecure deserialization - Java

Insecure deserialization - Java

Need

Secure deserialization process

Context

  • Usage of Java 8 for developing applications in the Java programming language
  • Usage of javax.servlet-api for building Java web applications with Servlets

Description

Non compliant code

        import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.InputStream;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class InsecureDeserializationServlet extends HttpServlet {
    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        try {
            InputStream inputStream = request.getInputStream();
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream);
            Object object = objectInputStream.readObject();
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
        
        

The above code represents a servlet in a Java backend application that reads a serialized object from the HTTP request stream and deserializes it without any validation or type checking.

The doPost method gets the input stream from the request, wraps it in an ObjectInputStream and then reads an object from the stream using the readObject method.

This is insecure because an attacker can create a malicious payload, serialize it and then send it to the server. The server will then deserialize the object without checking its content or type, leading to potential security vulnerabilities such as remote code execution, privilege escalation, or denial of service attacks.

The vulnerability lies in the fact that the application blindly trusts the incoming data and does not perform any validation checks before deserializing the object. This is a classic example of insecure deserialization.

The best practice to mitigate this risk is to avoid deserialization of untrusted data. If this is not possible, you should implement strict type constraints during the deserialization process and also apply input validation techniques to ensure the integrity of the data.

Steps

  • Validate the deserialized object's content
  • Cast the deserialized object to a specific type
  • Implement a whitelist or blacklist approach to restrict the types of objects that can be deserialized
  • Use a secure serialization mechanism, such as JSON or XML, instead of Java serialization

Compliant code

        import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectStreamClass;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

public class SecureDeserializationServlet extends HttpServlet {
    private static final List WHITELIST_CLASSES = Arrays.asList("com.example.Class1", "com.example.Class2");

    protected void doPost(HttpServletRequest request, HttpServletResponse response) {
        try {
            InputStream inputStream = request.getInputStream();
            ObjectInputStream objectInputStream = new ObjectInputStream(inputStream) {
                @Override
                protected Class resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
                    if (!WHITELIST_CLASSES.contains(desc.getName())) {
                        throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
                    }
                    return super.resolveClass(desc);
                }
            };
            Object object = objectInputStream.readObject();
            if (object instanceof com.example.Class1) {
                com.example.Class1 class1Object = (com.example.Class1) object;
                // process class1Object
            } else if (object instanceof com.example.Class2) {
                com.example.Class2 class2Object = (com.example.Class2) object;
                // process class2Object
            }
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
        
        

The above code fixes the insecure deserialization vulnerability by implementing a whitelist approach to restrict the types of objects that can be deserialized.

The resolveClass method in ObjectInputStream is overridden to check if the class of the object being deserialized is in the whitelist. If it is not, an InvalidClassException is thrown, preventing the deserialization of unauthorized classes.

The deserialized object is then cast to a specific type before being processed. This ensures that the object is of the expected type and prevents ClassCastException at runtime.

Please replace com.example.Class1 and com.example.Class2 with the actual classes that you expect to deserialize.

This approach significantly reduces the risk of insecure deserialization attacks by ensuring that only authorized and expected classes can be deserialized. However, for a more secure approach, consider using a secure serialization mechanism, such as JSON or XML, instead of Java serialization.

References