Introduction
Getting Started
- QuickStart
Patterns
- Languages
- Security
- Injection Vulnerabilities
- Authentication Vulnerabilities
- Authorization Vulnerabilities
- Cryptography Vulnerabilities
- Data Protection Vulnerabilities
- Input Validation Vulnerabilities
- Session Management Vulnerabilities
- Error Handling Vulnerabilities
- Logging Vulnerabilities
- Configuration Vulnerabilities
- File Handling Vulnerabilities
- Memory Management Vulnerabilities
- API Security Vulnerabilities
- Cross-Site Scripting Vulnerabilities
- Cross-Site Request Forgery Vulnerabilities
- Insecure Deserialization Vulnerabilities
- Security Misconfiguration Vulnerabilities
- Broken Access Control Vulnerabilities
- Sensitive Data Exposure Vulnerabilities
- XML Vulnerabilities
- Regular Expression Denial of Service (ReDoS)
- Performance
Integrations
- Code Repositories
- Team Messengers
- Ticketing
Enterprise
Insecure deserialization vulnerabilities occur when applications deserialize untrusted data without proper validation, potentially leading to remote code execution, denial of service, or privilege escalation.
Insecure deserialization vulnerabilities occur when applications deserialize untrusted data without proper validation or protection. Serialization is the process of converting complex data structures into a format that can be stored or transmitted, while deserialization is the reverse process of reconstructing these data structures.
When an application deserializes untrusted data, attackers can manipulate the serialized data to achieve various attacks, including remote code execution, denial of service, authentication bypass, or privilege escalation.
Preventing insecure deserialization requires implementing proper input validation, using secure deserialization libraries, and adopting safer alternatives to native serialization mechanisms.
// Anti-pattern: Unsafe Java deserialization
import java.io.ObjectInputStream;
import java.io.ByteArrayInputStream;
import java.util.Base64;
public class UserManager {
public User loadUserFromToken(String token) {
try {
// Decode the base64 token
byte[] serializedData = Base64.getDecoder().decode(token);
// Deserialize without any validation
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(serializedData));
// Unsafe deserialization of untrusted data
return (User) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
// Better approach: Using a safer alternative
import com.fasterxml.jackson.databind.ObjectMapper;
public class UserManager {
private final ObjectMapper objectMapper = new ObjectMapper();
public User loadUserFromToken(String token) {
try {
// Decode the base64 token
byte[] data = Base64.getDecoder().decode(token);
String json = new String(data, "UTF-8");
// Use JSON deserialization instead of native Java serialization
return objectMapper.readValue(json, User.class);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
// Even better: Using a whitelist approach with Jackson
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
public class UserManager {
private final ObjectMapper objectMapper;
public UserManager() {
// Configure Jackson with a whitelist of allowed classes
BasicPolymorphicTypeValidator validator = BasicPolymorphicTypeValidator.builder()
.allowIfBaseType(User.class)
.allowIfSubType("com.example.model.")
.build();
objectMapper = JsonMapper.builder()
.activateDefaultTyping(validator, ObjectMapper.DefaultTyping.NON_FINAL)
.build();
}
public User loadUserFromToken(String token) {
try {
byte[] data = Base64.getDecoder().decode(token);
String json = new String(data, "UTF-8");
return objectMapper.readValue(json, User.class);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
}
Java’s native serialization mechanism is particularly vulnerable to deserialization attacks, as it allows for arbitrary code execution during the deserialization process.
To prevent unsafe Java deserialization:
- Avoid using Java’s native serialization (ObjectInputStream) for untrusted data
- Use safer alternatives like JSON, XML, or YAML with appropriate libraries
- If native serialization is necessary, implement a whitelist of allowed classes
- Consider using serialization filtering in Java 9+ (ObjectInputFilter)
- Keep all libraries and dependencies updated
- Implement proper exception handling
- Consider using the OWASP SerialKiller or other serialization security libraries
// Anti-pattern: Unsafe PHP deserialization
<?php
// User data stored in a cookie
$userData = $_COOKIE['user_data'];
// Unsafe deserialization of untrusted data
$user = unserialize($userData);
if ($user->isAdmin) {
// Grant admin access
showAdminPanel();
}
?>
// Better approach: Using safer alternatives
<?php
// Option 1: Using JSON instead of PHP serialization
$userData = $_COOKIE['user_data'];
// JSON decode with assoc=true to get an array instead of objects
$userData = json_decode($userData, true);
// Validate the data structure
if (!isset($userData['username']) || !isset($userData['role'])) {
die('Invalid user data');
}
// Use the data safely
if ($userData['role'] === 'admin') {
// Verify admin status from database, not just from the cookie
if (verifyAdminStatus($userData['username'])) {
showAdminPanel();
}
}
// Option 2: If serialization is necessary, use a whitelist approach
function safeUnserialize($serialized) {
// Define allowed classes
$allowedClasses = ['UserData', 'UserPreferences'];
// Set up a whitelist for allowed classes
$options = ['allowed_classes' => $allowedClasses];
// Unserialize with options (PHP 7+)
return unserialize($serialized, $options);
}
$userData = $_COOKIE['user_data'];
$user = safeUnserialize($userData);
?>
PHP’s unserialize() function can lead to object injection vulnerabilities if used with untrusted data, potentially allowing attackers to instantiate arbitrary classes and trigger unexpected code execution.
To prevent unsafe PHP deserialization:
- Avoid using unserialize() with untrusted data
- Use JSON or other safer formats instead
- If serialization is necessary, use the allowed_classes option in PHP 7+
- Implement proper input validation
- Consider using cryptographic signatures to verify serialized data integrity
- Keep PHP and all libraries updated
- Implement proper exception handling
# Anti-pattern: Unsafe Python deserialization
import pickle
import base64
def load_user_preferences(token):
# Decode the base64 token
serialized_data = base64.b64decode(token)
# Unsafe deserialization of untrusted data
user_prefs = pickle.loads(serialized_data)
return user_prefs
# Better approach: Using safer alternatives
import json
import base64
def load_user_preferences(token):
try:
# Decode the base64 token
data = base64.b64decode(token)
# Use JSON instead of pickle
user_prefs = json.loads(data.decode('utf-8'))
# Validate the structure
if not isinstance(user_prefs, dict):
raise ValueError("Invalid user preferences format")
# Additional validation of expected fields
required_fields = ['theme', 'language', 'notifications']
for field in required_fields:
if field not in user_prefs:
raise ValueError(f"Missing required field: {field}")
return user_prefs
except Exception as e:
# Log the error
print(f"Error loading user preferences: {e}")
# Return default preferences
return {'theme': 'default', 'language': 'en', 'notifications': True}
# If complex objects are needed, use a safer serialization library
import marshmallow
class UserPreferencesSchema(marshmallow.Schema):
theme = marshmallow.fields.String(required=True)
language = marshmallow.fields.String(required=True)
notifications = marshmallow.fields.Boolean(required=True)
def load_user_preferences_with_schema(token):
try:
data = base64.b64decode(token)
json_data = json.loads(data.decode('utf-8'))
# Use schema to validate and deserialize
schema = UserPreferencesSchema()
user_prefs = schema.load(json_data)
return user_prefs
except Exception as e:
print(f"Error loading user preferences: {e}")
return {'theme': 'default', 'language': 'en', 'notifications': True}
Python’s pickle module is notoriously dangerous for deserializing untrusted data, as it can execute arbitrary code during deserialization.
To prevent unsafe Python deserialization:
- Never use pickle, marshal, or shelve modules with untrusted data
- Use safer alternatives like JSON, YAML, or MessagePack
- Implement proper input validation
- Consider using schema validation libraries like marshmallow or pydantic
- If complex object serialization is needed, consider libraries like jsonpickle with safe_mode enabled
- Implement proper exception handling
- Keep all dependencies updated
// Anti-pattern: Unsafe Node.js deserialization
const serialize = require('node-serialize');
function getUserData(userToken) {
// Decode the base64 token
const serializedData = Buffer.from(userToken, 'base64').toString();
// Unsafe deserialization of untrusted data
return serialize.unserialize(serializedData);
}
// Better approach: Using safer alternatives
function getUserData(userToken) {
try {
// Decode the base64 token
const data = Buffer.from(userToken, 'base64').toString();
// Use JSON.parse instead of node-serialize
const userData = JSON.parse(data);
// Validate the structure
if (!userData || typeof userData !== 'object') {
throw new Error('Invalid user data format');
}
// Additional validation of expected fields
const requiredFields = ['id', 'username', 'role'];
for (const field of requiredFields) {
if (!(field in userData)) {
throw new Error(`Missing required field: ${field}`);
}
}
return userData;
} catch (error) {
console.error('Error parsing user data:', error);
return null;
}
}
// If complex objects are needed, use a schema validation library
const Joi = require('joi');
function getUserDataWithSchema(userToken) {
try {
const data = Buffer.from(userToken, 'base64').toString();
const userData = JSON.parse(data);
// Define a schema for validation
const userSchema = Joi.object({
id: Joi.string().required(),
username: Joi.string().alphanum().min(3).max(30).required(),
role: Joi.string().valid('user', 'admin', 'moderator').required(),
preferences: Joi.object({
theme: Joi.string().valid('light', 'dark', 'system').default('system'),
notifications: Joi.boolean().default(true)
}).default()
});
// Validate against the schema
const { error, value } = userSchema.validate(userData);
if (error) {
throw error;
}
return value;
} catch (error) {
console.error('Error parsing user data:', error);
return null;
}
}
Node.js applications can be vulnerable to deserialization attacks, particularly when using libraries like node-serialize that support serializing functions.
To prevent unsafe Node.js deserialization:
- Avoid using libraries that support serializing functions (like node-serialize)
- Use JSON.parse for deserialization of untrusted data
- Implement proper input validation
- Consider using schema validation libraries like Joi or Yup
- Implement proper error handling
- Keep all dependencies updated
- Consider using Object.freeze() to prevent modification of deserialized objects
// Anti-pattern: Unsafe deserialization in API
const express = require('express');
const app = express();
const serialize = require('node-serialize');
app.use(express.text());
app.post('/api/process-data', (req, res) => {
try {
// Deserialize data from request body
const data = serialize.unserialize(req.body);
// Process the data
processData(data);
res.json({ success: true });
} catch (error) {
res.status(500).json({ error: error.message });
}
});
// Better approach: Safe API deserialization
const express = require('express');
const app = express();
const Joi = require('joi');
// Use JSON middleware instead of text
app.use(express.json());
// Define a schema for validation
const dataSchema = Joi.object({
id: Joi.string().required(),
name: Joi.string().required(),
values: Joi.array().items(Joi.number()).required(),
metadata: Joi.object().pattern(Joi.string(), Joi.string()).optional()
});
app.post('/api/process-data', (req, res) => {
try {
// Validate request body against schema
const { error, value } = dataSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.message });
}
// Process the validated data
processData(value);
res.json({ success: true });
} catch (error) {
console.error('Error processing data:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
APIs that accept serialized data from clients are particularly vulnerable to deserialization attacks, as they often process untrusted data from various sources.
To prevent deserialization vulnerabilities in APIs:
- Use safe formats like JSON for data exchange
- Implement proper input validation and schema validation
- Set strict content type requirements
- Implement proper error handling without exposing sensitive details
- Use rate limiting to prevent DoS attacks via deserialization
- Implement proper logging and monitoring
- Keep all dependencies updated
- Consider implementing API gateways with additional security controls
// Anti-pattern: Vulnerable to gadget chain attacks
import java.io.ObjectInputStream;
import java.io.ByteArrayInputStream;
import java.util.Base64;
public class DataProcessor {
public void processData(String serializedData) {
try {
byte[] data = Base64.getDecoder().decode(serializedData);
// Vulnerable to gadget chain attacks
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(data));
Object obj = ois.readObject();
processObject(obj);
} catch (Exception e) {
e.printStackTrace();
}
}
private void processObject(Object obj) {
// Process the deserialized object
}
}
// Better approach: Using serialization filters
import java.io.ObjectInputStream;
import java.io.ByteArrayInputStream;
import java.io.InvalidClassException;
import java.util.Base64;
import java.io.ObjectInputFilter;
public class DataProcessor {
public void processData(String serializedData) {
try {
byte[] data = Base64.getDecoder().decode(serializedData);
ByteArrayInputStream bais = new ByteArrayInputStream(data);
ObjectInputStream ois = new ObjectInputStream(bais);
// Set up serialization filter (Java 9+)
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(
"com.example.model.*;java.util.ArrayList;java.util.HashMap");
ois.setObjectInputFilter(filter);
Object obj = ois.readObject();
processObject(obj);
} catch (InvalidClassException ice) {
System.err.println("Security violation: " + ice.getMessage());
} catch (Exception e) {
e.printStackTrace();
}
}
private void processObject(Object obj) {
// Process the deserialized object
}
}
Gadget chains are a particularly dangerous aspect of deserialization vulnerabilities, where attackers chain together multiple classes to achieve malicious outcomes during deserialization.
To prevent gadget chain attacks:
- Avoid using native serialization mechanisms with untrusted data
- Implement class whitelisting for deserialization
- Use serialization filters (e.g., ObjectInputFilter in Java 9+)
- Keep all libraries and dependencies updated
- Consider using deserialization security libraries
- Implement proper exception handling
- Consider using runtime application self-protection (RASP) solutions
// Anti-pattern: Vulnerable to DoS via deserialization
import java.io.ObjectInputStream;
import java.io.ByteArrayInputStream;
public class MessageProcessor {
public void processMessage(byte[] serializedData) {
try {
// No size limits or timeouts
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(serializedData));
Object message = ois.readObject();
handleMessage(message);
} catch (Exception e) {
e.printStackTrace();
}
}
private void handleMessage(Object message) {
// Process the message
}
}
// Better approach: Implementing protections against DoS
import java.io.ObjectInputStream;
import java.io.ByteArrayInputStream;
import java.util.concurrent.*;
public class MessageProcessor {
private static final int MAX_DATA_SIZE = 1024 * 1024; // 1MB
private static final int DESERIALIZATION_TIMEOUT = 5; // 5 seconds
public void processMessage(byte[] serializedData) {
// Check data size
if (serializedData.length > MAX_DATA_SIZE) {
System.err.println("Data too large: " + serializedData.length + " bytes");
return;
}
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Object> future = executor.submit(() -> {
try {
ObjectInputStream ois = new ObjectInputStream(
new ByteArrayInputStream(serializedData));
// Set up serialization filter
ois.setObjectInputFilter(createSerializationFilter());
return ois.readObject();
} catch (Exception e) {
throw new RuntimeException(e);
}
});
try {
// Apply timeout to deserialization
Object message = future.get(DESERIALIZATION_TIMEOUT, TimeUnit.SECONDS);
handleMessage(message);
} catch (TimeoutException e) {
System.err.println("Deserialization timed out");
future.cancel(true);
} catch (Exception e) {
System.err.println("Error during deserialization: " + e.getMessage());
} finally {
executor.shutdownNow();
}
}
private java.io.ObjectInputFilter createSerializationFilter() {
return java.io.ObjectInputFilter.Config.createFilter(
"com.example.model.*;java.util.ArrayList;!*");
}
private void handleMessage(Object message) {
// Process the message
}
}
Deserialization can be exploited for denial of service attacks by crafting serialized data that consumes excessive resources during deserialization.
To prevent deserialization denial of service:
- Implement size limits for serialized data
- Use timeouts for deserialization operations
- Implement resource limits (memory, CPU)
- Consider processing deserialization in a separate process or container
- Implement proper monitoring and alerting
- Use rate limiting for endpoints that accept serialized data
- Consider using more efficient serialization formats
- Implement circuit breakers for critical systems
// Pattern 1: Data Transfer Objects with manual deserialization
public class UserDTO {
private String username;
private String email;
private String role;
// Getters and setters
// Factory method for safe deserialization
public static UserDTO fromJson(String json) {
try {
// Use a JSON library
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(json);
UserDTO dto = new UserDTO();
// Manual extraction and validation
if (node.has("username") && node.get("username").isTextual()) {
dto.setUsername(node.get("username").asText());
} else {
throw new IllegalArgumentException("Invalid or missing username");
}
if (node.has("email") && node.get("email").isTextual()) {
String email = node.get("email").asText();
if (!isValidEmail(email)) {
throw new IllegalArgumentException("Invalid email format");
}
dto.setEmail(email);
} else {
throw new IllegalArgumentException("Invalid or missing email");
}
if (node.has("role") && node.get("role").isTextual()) {
String role = node.get("role").asText();
if (!isValidRole(role)) {
throw new IllegalArgumentException("Invalid role");
}
dto.setRole(role);
} else {
// Default role if not specified
dto.setRole("user");
}
return dto;
} catch (Exception e) {
throw new IllegalArgumentException("Error deserializing user data", e);
}
}
private static boolean isValidEmail(String email) {
// Email validation logic
return email.matches("^[A-Za-z0-9+_.-]+@(.+)$");
}
private static boolean isValidRole(String role) {
// Role validation logic
return Arrays.asList("user", "admin", "moderator").contains(role);
}
}
// Pattern 2: Builder pattern with validation
public class User {
private final String username;
private final String email;
private final String role;
private User(Builder builder) {
this.username = builder.username;
this.email = builder.email;
this.role = builder.role;
}
// Getters only, no setters for immutability
public static class Builder {
private String username;
private String email;
private String role = "user"; // Default value
public Builder username(String username) {
this.username = username;
return this;
}
public Builder email(String email) {
this.email = email;
return this;
}
public Builder role(String role) {
this.role = role;
return this;
}
public User build() {
// Validate before creating object
if (username == null || username.isEmpty()) {
throw new IllegalArgumentException("Username cannot be empty");
}
if (email == null || !email.matches("^[A-Za-z0-9+_.-]+@(.+)$")) {
throw new IllegalArgumentException("Invalid email format");
}
if (!Arrays.asList("user", "admin", "moderator").contains(role)) {
throw new IllegalArgumentException("Invalid role");
}
return new User(this);
}
}
// Factory method for JSON deserialization
public static User fromJson(String json) {
try {
ObjectMapper mapper = new ObjectMapper();
JsonNode node = mapper.readTree(json);
Builder builder = new Builder();
if (node.has("username")) {
builder.username(node.get("username").asText());
}
if (node.has("email")) {
builder.email(node.get("email").asText());
}
if (node.has("role")) {
builder.role(node.get("role").asText());
}
// Validation happens in build()
return builder.build();
} catch (Exception e) {
throw new IllegalArgumentException("Error deserializing user data", e);
}
}
}
Implementing secure deserialization patterns can help mitigate the risks associated with deserializing untrusted data.
Key secure deserialization patterns:
- Use Data Transfer Objects (DTOs) with manual deserialization
- Implement the Builder pattern with validation
- Use immutable objects to prevent post-deserialization manipulation
- Implement factory methods for controlled object creation
- Use schema validation libraries
- Implement proper input validation
- Consider using safer serialization formats
- Implement proper exception handling
Insecure Deserialization Prevention Checklist:
1. Avoid Native Serialization
- Avoid using native serialization mechanisms (Java ObjectInputStream, PHP unserialize(), Python pickle, etc.)
- Use safer alternatives like JSON, XML, or YAML with appropriate libraries
- If native serialization is necessary, implement strict controls
2. Input Validation
- Validate all serialized data before deserialization
- Implement schema validation
- Use whitelisting instead of blacklisting
- Validate data structure, types, and values
3. Implement Class Restrictions
- Use whitelists of allowed classes for deserialization
- Implement serialization filters
- Avoid deserializing arbitrary classes
4. Integrity Verification
- Implement cryptographic signatures for serialized data
- Verify data integrity before deserialization
- Use HMAC or digital signatures
5. Resource Protection
- Implement size limits for serialized data
- Use timeouts for deserialization operations
- Implement resource limits (memory, CPU)
- Consider processing in isolated environments
6. Secure Libraries and Dependencies
- Keep all libraries and dependencies updated
- Use security-focused serialization libraries
- Regularly check for vulnerabilities in dependencies
7. Monitoring and Logging
- Implement proper logging for deserialization operations
- Monitor for suspicious deserialization attempts
- Implement alerts for potential attacks
Preventing insecure deserialization requires a comprehensive approach that addresses multiple aspects of the deserialization process.
Key prevention strategies:
- Use safer alternatives to native serialization
- Implement proper input validation and integrity verification
- Restrict which classes can be deserialized
- Protect against resource exhaustion
- Keep all dependencies updated
- Implement proper monitoring and logging
- Follow the principle of defense in depth