Insecure Deserialization on Docker
How Insecure Deserialization Manifests in Docker
Insecure deserialization in Docker environments often stems from how containers handle serialized data across the application lifecycle. When Docker containers communicate with each other or with external services, serialized objects frequently traverse network boundaries, making them prime targets for exploitation.
A common Docker-specific scenario involves serialized Python objects stored in Redis or other in-memory databases. Consider a Flask application running in a container that uses Python's pickle module to serialize user session data:
import pickle
from flask import Flask, session, request
app = Flask(__name__)
app.secret_key = 'supersecretkey'
@app.route('/set_session')
def set_session():
data = request.args.get('data')
session['user_data'] = pickle.dumps(data)
return 'Session set'
@app.route('/get_session')
def get_session():
data = pickle.loads(session.get('user_data', ''))
return f'Session data: {data}'
The vulnerability here is that pickle.loads() can execute arbitrary code during deserialization. An attacker could craft a malicious pickle payload that executes system commands when deserialized:
import pickle
import os
class Malicious(object):
def __reduce__(self):
return (os.system, ('rm -rf /',))
payload = pickle.dumps(Malicious())
When this payload is deserialized in the Flask container, it could delete critical files or execute other malicious commands. The containerized nature of Docker doesn't inherently protect against this—if the container has elevated privileges or mounts sensitive volumes, the impact can be severe.
Another Docker-specific manifestation occurs with Java applications using native serialization in microservices architectures. When containers communicate via serialized Java objects over REST endpoints or message queues, attackers can exploit ObjectInputStream vulnerabilities:
import java.io.*;
import javax.ws.rs.*;
@Path('/deserialize')
public class DeserializationResource {
@POST
@Consumes(MediaType.APPLICATION_OCTET_STREAM)
public String deserialize(byte[] data) throws IOException, ClassNotFoundException {
ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(data));
Object obj = ois.readObject();
return obj.toString();
}
}
In Docker Swarm or Kubernetes environments, serialized objects might travel between nodes, and if one compromised container can influence the serialization format, it could lead to remote code execution across the entire cluster.
Docker-Specific Detection
Detecting insecure deserialization in Docker environments requires both static analysis of container images and runtime monitoring of container behavior. middleBrick's Docker-specific scanning capabilities examine the runtime attack surface without requiring access to source code or container internals.
middleBrick scans API endpoints exposed by Docker containers for deserialization vulnerabilities by testing common attack vectors. For instance, it sends serialized payloads to endpoints that accept binary data and monitors for signs of successful exploitation:
# Example middleBrick CLI scan for a Dockerized API
middlebrick scan https://api.myapp.com/ --format json
The scanner tests for known deserialization gadget chains specific to popular libraries like Python's pickle, Java's native serialization, and .NET's BinaryFormatter. It also checks for endpoints that accept serialized data without proper validation.
For Docker-specific detection, middleBrick examines:
- Endpoints that accept Content-Type: application/octet-stream or similar binary formats
- API endpoints that might be processing serialized session data
- Microservices communication channels where objects are serialized across container boundaries
- Configuration endpoints that might deserialize YAML or XML data
Beyond automated scanning, Docker-specific detection includes monitoring container logs for deserialization-related errors and using Docker's security scanning features to identify vulnerable libraries in container images:
# Scan Docker image for known vulnerabilities
docker run --rm -v /var/run/docker.sock:/var/run/docker.sock
aquasec/trivy image myapp:latest
This combination of runtime API scanning and container image analysis provides comprehensive coverage of deserialization risks in Docker environments.
Docker-Specific Remediation
Remediating insecure deserialization in Docker requires both code-level fixes and container configuration changes. The most effective approach combines secure coding practices with Docker's native security features.
For Python applications, replace pickle with safer alternatives like JSON for data serialization:
import json
from flask import Flask, session, request
app = Flask(__name__)
app.secret_key = 'supersecretkey'
@app.route('/set_session')
def set_session():
data = request.args.get('data')
session['user_data'] = json.dumps(data)
return 'Session set'
@app.route('/get_session')
def get_session():
data = json.loads(session.get('user_data', 'null'))
return f'Session data: {data}'
For Java applications, use safe deserialization libraries or implement strict class filtering:
import java.io.*;
import java.util.*;
public class SafeDeserialization {
public static Object deserialize(byte[] data) throws IOException, ClassNotFoundException {
ByteArrayInputStream bis = new ByteArrayInputStream(data);
ObjectInputStream ois = new LookAheadObjectInputStream(bis);
return ois.readObject();
}
private static class LookAheadObjectInputStream extends ObjectInputStream {
private static final List<Class<?>> SAFE_CLASSES = Arrays.asList(
String.class,
Integer.class,
ArrayList.class
);
public LookAheadObjectInputStream(InputStream in) throws IOException {
super(in);
}
@Override
protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
Class<?> clazz = super.resolveClass(desc);
if (!SAFE_CLASSES.contains(clazz)) {
throw new InvalidClassException("Unauthorized deserialization attempt", desc.getName());
}
return clazz;
}
}
}
Docker-specific remediation includes running containers with minimal privileges and using Docker's security features:
# Dockerfile with security best practices
FROM python:3.9-slim
# Create non-root user
RUN useradd -m -s /bin/bash appuser
# Install only necessary packages
RUN apt-get update && apt-get install -y --no-install-recommends \
&& rm -rf /var/lib/apt/lists/*
# Copy application code
COPY . /app
WORKDIR /app
# Switch to non-root user
USER appuser
# Expose only necessary ports
EXPOSE 8000
# Run application with restricted capabilities
CMD ["python", "app.py"]
Additionally, use Docker's seccomp profiles to restrict system calls that could be exploited through deserialization vulnerabilities:
# seccomp profile to limit dangerous syscalls
{
"defaultAction": "SCMP_ACT_ERRNO",
"architectures": ["SCMP_ARCH_X86_64", "SCMP_ARCH_X86"],
"syscalls": [
{
"names": ["write", "read", "exit", "sigreturn"],
"action": "SCMP_ACT_ALLOW"
}
]
}
Run containers with this profile using:
docker run --security-opt seccomp=seccomp.json myapp
For production deployments, combine these techniques with network segmentation so that even if a container is compromised through deserialization, lateral movement is limited. Use Docker secrets for sensitive data rather than storing it in serialized objects that could be extracted.