Unsafe Java Object Deserialization Leading to RCE

Critical Risk Command Injection
javadeserializationserializationobjectinputstreamrcegadget-chainremote-code-execution

What it is

A critical security vulnerability where untrusted serialized Java objects are deserialized without proper validation, allowing attackers to execute arbitrary code. This occurs when applications accept serialized objects from untrusted sources and deserialize them using ObjectInputStream, enabling exploitation through gadget chains in the application's classpath.

import java.io.*;
import javax.servlet.http.*;
import java.util.*;

// VULNERABLE: Direct deserialization of untrusted data
public class VulnerableServer {
    
    public void handleRequest(InputStream input) throws Exception {
        ObjectInputStream ois = new ObjectInputStream(input);
        
        // VULNERABLE: Deserializes untrusted data
        // Attacker can send malicious serialized objects
        Object userObject = ois.readObject();
        
        processUserData(userObject);
    }
    
    // VULNERABLE: HTTP endpoint accepting serialized objects
    @WebServlet("/api/data")
    public class DataServlet extends HttpServlet {
        protected void doPost(HttpServletRequest request, 
                            HttpServletResponse response) throws Exception {
            
            byte[] data = request.getInputStream().readAllBytes();
            
            // VULNERABLE: Deserializing HTTP body
            ObjectInputStream ois = new ObjectInputStream(
                new ByteArrayInputStream(data)
            );
            
            Object payload = ois.readObject();
            
            response.getWriter().println("Data processed");
        }
    }
    
    // VULNERABLE: Session deserialization
    public Map<String, Object> loadSession(String sessionData) throws Exception {
        byte[] data = Base64.getDecoder().decode(sessionData);
        
        ObjectInputStream ois = new ObjectInputStream(
            new ByteArrayInputStream(data)
        );
        
        // VULNERABLE: User controls serialized session
        return (Map<String, Object>) ois.readObject();
    }
}
import com.fasterxml.jackson.databind.*;
import java.io.*;
import javax.servlet.http.*;

// SECURE: Using JSON instead of Java serialization
public class SecureServer {
    private final ObjectMapper objectMapper;
    
    public SecureServer() {
        this.objectMapper = new ObjectMapper();
        objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, true);
    }
    
    public void handleRequest(InputStream input) throws Exception {
        // SECURE: JSON deserialization with type safety
        String jsonData = new String(input.readAllBytes());
        
        if (jsonData.length() > 10000) {
            throw new IllegalArgumentException("Data too large");
        }
        
        // Deserialize to specific type, not Object
        UserData userData = objectMapper.readValue(jsonData, UserData.class);
        validateUserData(userData);
        processUserData(userData);
    }
    
    // SECURE: HTTP endpoint with JSON
    @WebServlet("/api/data")
    public class DataServlet extends HttpServlet {
        protected void doPost(HttpServletRequest request, HttpServletResponse response) 
                throws Exception {
            String jsonBody = request.getReader().lines().collect(Collectors.joining());
            
            if (jsonBody.length() > 10000) {
                response.sendError(400, "Request too large");
                return;
            }
            
            // SECURE: JSON to specific type
            UserData data = objectMapper.readValue(jsonBody, UserData.class);
            validateUserData(data);
            response.getWriter().println("Data processed");
        }
    }
    
    private void validateUserData(UserData userData) {
        if (userData.getName() == null || userData.getName().length() > 100) {
            throw new IllegalArgumentException("Invalid name");
        }
    }
}

public class UserData {
    private String name;
    private String email;
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
    public void setEmail(String email) { this.email = email; }
}

💡 Why This Fix Works

The vulnerable code uses ObjectInputStream.readObject() to deserialize untrusted data from network streams, HTTP requests, and session data, allowing attackers to execute arbitrary code through gadget chains. The secure version replaces Java serialization with JSON deserialization using Jackson, which doesn't execute code and deserializes to specific types with validation.

Why it happens

Java applications use ObjectInputStream.readObject() to deserialize data from untrusted sources like network sockets, HTTP requests, or user-uploaded files. This allows attackers to craft malicious serialized objects that execute code during deserialization through gadget chains.

Root causes

ObjectInputStream with Untrusted Data

Java applications use ObjectInputStream.readObject() to deserialize data from untrusted sources like network sockets, HTTP requests, or user-uploaded files. This allows attackers to craft malicious serialized objects that execute code during deserialization through gadget chains.

Serialized Objects in HTTP Requests

Applications accept and deserialize Java serialized objects from HTTP POST parameters, request bodies, or API endpoints without validation. Attackers can send crafted serialized payloads that trigger remote code execution when deserialized by the server.

Unvalidated Session Data Deserialization

Session data or cookies containing serialized Java objects are deserialized without verification or integrity checks. Attackers can modify these serialized sessions to inject malicious objects that execute arbitrary code when the application deserializes them.

Exposed RMI and JMX Services

Java RMI (Remote Method Invocation) and JMX (Java Management Extensions) services exposed to untrusted networks automatically deserialize objects from remote clients. Attackers can connect to these services and send malicious serialized objects for code execution.

Vulnerable Gadget Chain Libraries

Applications have vulnerable libraries like commons-collections, commons-beanutils, Spring, or Groovy in their classpath. These libraries contain classes that can be chained together (gadget chains) to achieve arbitrary code execution during deserialization.

Missing Deserialization Filters

No deserialization filters or class allowlists implemented to restrict which classes can be deserialized. Without filtering, attackers can deserialize any class available in the application's classpath, including dangerous gadget chain classes.

Fixes

1

Replace Java Serialization with Safe Formats

Migrate from Java serialization to safer data exchange formats like JSON (using Jackson or Gson), XML, Protocol Buffers, or MessagePack. These formats don't execute code during parsing and provide better security guarantees than native Java serialization.

2

Implement ObjectInputFilter Deserialization Filters

Use Java's ObjectInputFilter (Java 9+) or third-party filtering libraries to restrict which classes can be deserialized. Configure filters to reject dangerous classes and only allow a specific allowlist of safe, expected classes needed by the application.

3

Enforce Strict Class Allowlists

Create and maintain explicit allowlists of classes permitted for deserialization. Use libraries like notsoserial or SerialKiller to implement class filtering. Reject any deserialization attempt for classes not on the allowlist, including all gadget chain classes.

4

Remove or Upgrade Vulnerable Libraries

Audit application dependencies and remove or upgrade libraries known to contain deserialization gadget chains (e.g., commons-collections ≤ 3.2.1, commons-beanutils, Spring Framework older versions). Use tools like GadgetProbe or ysoserial to identify risky dependencies.

5

Never Deserialize Untrusted Data

Design applications to avoid deserializing data from untrusted sources entirely. If receiving data from external systems, use safe data formats and explicit parsing. Treat all deserialization of external data as a critical security operation requiring extensive validation.

6

Validate Input Before Deserialization

Implement comprehensive input validation including HMAC signature verification, structure validation, and size limits before attempting deserialization. Use cryptographic signatures to ensure data hasn't been tampered with and originates from trusted sources.

Detect This Vulnerability in Your Code

Sourcery automatically identifies unsafe java object deserialization leading to rce and many other security issues in your codebase.