Insecure Design on Docker
How Insecure Design Manifests in Docker
Insecure Design in Docker environments often stems from architectural decisions that expose critical attack surfaces. A common manifestation is improper container isolation, where containers share namespaces or run with excessive privileges. For example, mounting the Docker socket (/var/run/docker.sock) into a container grants it root-level control over the host:
# Insecure: mounts Docker socket, allowing container to control host
apiVersion: v1
spec:
containers:
- name: vulnerable-app
image: myapp:latest
volumeMounts:
- mountPath: /var/run/docker.sock
name: docker-socket
volumes:
- name: docker-socket
hostPath:
path: /var/run/docker.sock
This design flaw enables container breakout attacks. An attacker who compromises the container can execute docker run commands on the host, install malicious images, or even delete critical system containers.
Another Docker-specific insecure design pattern involves running containers as root. By default, containers run with UID 0, which is unnecessary for most applications and creates privilege escalation risks:
# Insecure: runs as root
FROM node:18-alpine
USER root
WORKDIR /app
COPY . .
RUN npm install
CMD ["node", "server.js"]
If an attacker exploits a vulnerability in the Node.js application, they gain immediate root access within the container, potentially allowing host filesystem access through mounted volumes or kernel exploits.
Container networking design flaws also contribute to insecure design. Exposing unnecessary ports or using the host network mode removes network isolation:
# Insecure: uses host networking, shares host interfaces
apiVersion: v1
spec:
containers:
- name: app
image: myapp:latest
hostNetwork: true
ports:
- containerPort: 8080
This design allows the container to bind to any port on the host's network interfaces, potentially conflicting with existing services or exposing internal APIs to unintended networks.
Docker-Specific Detection
Detecting insecure design in Docker requires examining both configuration files and runtime behavior. Start by scanning Docker Compose and Kubernetes manifests for privilege escalation indicators:
# Check for privileged containers and socket mounts
docker ps -q | xargs docker inspect --format='{{.Id}}: Privileged={{.HostConfig.Privileged}} CapAdd={{.HostConfig.CapAdd}} Mounts={{range .Mounts}}{{.Source}}->{{.Destination}} {{end}}'
# Find containers running as root
docker ps -q | xargs docker inspect --format='{{.Id}}: User={{.Config.User}}'
# Identify containers with host PID namespace
docker ps -q | xargs docker inspect --format='{{.Id}}: HostPID={{.HostConfig.PidMode}}'
These commands reveal containers running with elevated privileges, mounted Docker sockets, or sharing host namespaces—all indicators of insecure design.
middleBrick's Docker-specific scanning goes beyond basic inspection by actively testing attack vectors. The scanner examines container configurations for BOLA (Broken Object Level Authorization) patterns in container APIs, tests for privilege escalation through mounted volumes, and analyzes network exposure:
{
"docker_scan": {
"docker_socket_access": "CRITICAL",
"privileged_containers": 2,
"root_user_containers": 5,
"host_network_mode": 1,
"exposed_ports": ["8080", "3306", "5432"]
}
}
The scanner also tests for container escape vulnerabilities by attempting to access host resources through mounted volumes and analyzing container capabilities for dangerous permissions like NET_ADMIN or SYS_ADMIN.
Image scanning complements runtime detection by identifying vulnerable base images and hardcoded secrets. Tools like Trivy or Clair can be integrated into CI/CD pipelines to catch insecure design before deployment:
# Scan Docker image for vulnerabilities
trivy image myapp:latest --severity CRITICAL --output vulns.json
# Check for secrets in image layers
docker run --rm -v $(pwd):/src dockosec/secrets-scan:latest /src
Docker-Specific Remediation
Remediating insecure design in Docker requires architectural changes enforced through configuration. Start by eliminating root privileges using the USER directive in Dockerfiles:
FROM node:18-alpine
# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
adduser -u 1001 -S nodejs -G nodejs
WORKDIR /app
COPY package*.json ./
RUN npm ci --only=production
COPY . .
USER nodejs
EXPOSE 8080
CMD ["node", "server.js"]
This ensures the application runs with minimal privileges, limiting the impact of potential exploits.
Replace privileged containers with capability-based security using cap-drop and cap-add:
# Secure: minimal capabilities, no privileged mode
apiVersion: v1
spec:
containers:
- name: app
image: myapp:latest
securityContext:
runAsNonRoot: true
runAsUser: 1001
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
volumeMounts:
- mountPath: /app/data
name: data
readOnly: true
volumes:
- name: data
emptyDir: {}
This configuration drops all capabilities by default and only adds the specific capabilities needed for the application to function.
Network security requires isolating containers using user-defined networks instead of host networking:
# Create isolated network
docker network create --driver bridge --subnet 172.20.0.0/16 secure-net
# Run container with network isolation
docker run -d --network secure-net --name secure-app myapp:latest
For Kubernetes deployments, use Pod Security Standards to enforce security policies:
apiVersion: policy/v1beta1
kind: PodSecurityPolicy
metadata:
name: restricted
spec:
privileged: false
allowPrivilegeEscalation: false
requiredDropCapabilities:
- ALL
runAsUser:
rule: 'MustRunAsNonRoot'
seLinux:
rule: 'RunAsAny'
Implement runtime protection with AppArmor or SELinux profiles to enforce additional security boundaries:
# AppArmor profile for container confinement
docker run --security-opt apparmor=docker-default myapp:latest
Finally, integrate security scanning into your CI/CD pipeline to catch insecure design early:
# GitHub Actions workflow with security gates
name: CI
on: [push, pull_request]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build Docker image
run: docker build -t myapp .
- name: Security scan
run: |
docker run --rm -v $(pwd):/src dockosec/secrets-scan:latest /src
trivy image myapp:latest --severity HIGH,CRITICAL
- name: Deploy
if: success()
run: docker push myapp:latest