Container escape techniques exploit a fundamental misconception: that containers are "isolated." This mental model is responsible for more cloud security incidents than most practitioners acknowledge. Containers are a Linux kernel feature — namespaces plus cgroups — not a security boundary. They share the host kernel, and anything that reaches the kernel from inside a container is operating in the same security domain as everything else on the host. The additional configuration that turns container isolation into a security control (seccomp profiles, capability dropping, user namespaces, read-only root filesystems) is optional and disabled by default in most container runtimes. What ships by default is operational convenience, not security.
Privileged Containers: Explicit Kernel Access
A container launched with --privileged is not a container in any meaningful security sense. It receives all Linux capabilities, it can mount any filesystem on the host, it has access to all host devices, and the seccomp filter is disabled. The escape is straightforward: mount the host's root filesystem and write to it directly.
The technique used in most CTF challenges and real compromises is to mount the host disk device and then write a cron job, a new /etc/cron.d/ entry, or modify /etc/passwd to add a root account. From inside a privileged container, the host's /dev/sda1 (or whatever the root partition is) is accessible and mountable. This is not a kernel exploit — it is intended functionality being used as designed, but against the assumption that the container boundary provides protection.
Detection from the host: docker inspect <container_id> | grep Privileged returning true. In Kubernetes, a pod spec with securityContext.privileged: true at the container level is the equivalent.
The Docker Socket: A Root Shell Waiting
Mounting the Docker socket into a container is common practice for CI/CD pipelines, monitoring agents, and developer tooling. It is also a complete compromise of the host. The Docker socket at /var/run/docker.sock is the Unix socket that the Docker daemon listens on, and it provides an API that can create new containers. A process inside a container that has access to this socket can call the Docker API to launch a new privileged container with the host filesystem mounted — and then use that container to escape.
The attack chain: inside the compromised container, verify the socket exists with ls -la /var/run/docker.sock, then use the Docker CLI or a direct API call to create a new container that mounts / from the host, runs as root, and executes arbitrary commands. The new container is on the host's Docker daemon, not nested — so its filesystem access is to the real host root.
This vector — along with vulnerabilities like CVE-2019-5736 (runc container breakout via /proc/self/exe overwrite) — appears in legitimate infrastructure more often than privileged containers because the operational use case (giving a CI runner the ability to build and push images) feels reasonable. The risk is that any code executing inside that CI container — including attacker-controlled code from a supply chain compromise — inherits the same Docker socket access.
Host PID Namespace and Capability Abuse
Sharing the host PID namespace (--pid=host) makes all processes on the host visible to the container. This alone is not an escape, but combined with SYS_PTRACE capability, it enables attaching a debugger to any host process and injecting shellcode. The attack uses ptrace to attach to a root-owned process, inject a mmap + mprotect + shellcode sequence, and execute arbitrary code in the context of that host process.
SYS_ADMIN capability deserves special mention because it unlocks an enormous attack surface: mounting filesystems (including FUSE filesystems that can be used to confuse the kernel), loading kernel modules, modifying namespaces of other processes, and accessing hardware devices. A container with SYS_ADMIN and no other mitigations is nearly as dangerous as a privileged container.
The cap_sys_ptrace escape demonstrated at Black Hat USA 2019 ("A Compendium of Container Escapes" by Edwards and Freeman) showed that even without --privileged, a container with SYS_PTRACE and host PID namespace sharing could achieve full host compromise against an unpatched kernel via the /proc/<pid>/mem interface. The interface allows writing directly to another process's memory space without ptrace calls — bypassing some detection mechanisms.
Detecting Container Escape Attempts
Runtime security tools like Falco monitor syscall patterns and flag escape-indicative behavior. The rules that matter most: any mount syscall from inside a container, any attempt to create a new namespace that would break the expected hierarchy, any ptrace call targeting a PID outside the container's PID namespace, and any process inside a container executing from a path that did not exist at container start time (indicating code injection or fileless execution).
For a simpler detection approach, audit for containers that were launched with dangerous options by periodically running:
docker ps -q | xargs docker inspect --format '{{.Name}} Privileged:{{.HostConfig.Privileged}} PidMode:{{.HostConfig.PidMode}} Mounts:{{range .Mounts}}{{.Source}}{{end}}'
Any container where Privileged is true, PidMode is host, or the mounts include /var/run/docker.sock or / should trigger an immediate review.
Container Escape Prevention: Hardening That Works
Seccomp profiles restrict which syscalls a container can make. The default Docker seccomp profile blocks around 44 syscalls but still permits far more than most workloads need. A custom seccomp profile built for a specific application (using tools like seccomp-bpf or the strace-based profiling approach) can reduce the syscall surface to the exact set the application uses, making most kernel exploit techniques impossible by denying the underlying syscalls.
Capability dropping should be the default, not the exception. Most containerized applications need zero Linux capabilities. Start from --cap-drop=ALL and add back only what is actually required. Web servers typically need nothing. A process binding to a port below 1024 needs NET_BIND_SERVICE. Very few application containers need anything else.
Kubernetes Pod Security Standards (the replacement for deprecated PodSecurityPolicy) enforce these constraints at the cluster level. The restricted profile requires running as a non-root user, drops all capabilities, disallows privilege escalation, and requires a seccomp profile. A read-only root filesystem is strongly recommended but not enforced by the standard. Enforcing this profile on every namespace that does not have a documented exception eliminates the most common escape vectors before a pod is ever scheduled.
The fundamental lesson from container escape research is that every default that trades security for operational convenience is an attack surface. The containerization ecosystem defaulted toward ease of use, and the security controls were added as afterthoughts that administrators have to opt into. Treating those controls as optional extras rather than baseline requirements is the assumption that attackers rely on.
Top comments (0)