Containers are meant to keep processes contained. But there are ways to gather information about the host – like the actual execution environment you are running in.
Containers are pretty good at keeping everyone and everything inside their boundaries – thanks to SELinux, namespaces and so on. But they are not perfect. Thanks to a recent Azure security flaw I was made aware of a nice trick via the /proc/ file system to figure out what container runtime the container is running in.
The idea is that the started container inherits some
/proc/ entries – among the entry for
/proc/self/. If we are able to load a malicious container, we can use this knowledge to execute the container host binary and by that get information about the runtime.
As an example, let’s take a Fedora container image with podman (and all it’s library dependencies) installed. We can run it and check the version of crun inside:
❯ podman run --rm podman:v3.2.0 crun --version crun version 0.20.1 commit: 0d42f1109fd73548f44b01b3e84d04a279e99d2e spec: 1.0.0 +SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +YAJL
Note that I take an older version here to better highlight the difference between host and container binary!
If we now change the execution to
/proc/self/exe, which points to the host binary, the result shows a different version:
❯ podman run --rm podman:v3.2.0 /proc/self/exe --version crun version 1.0 commit: 139dc6971e2f1d931af520188763e984d6cdfbf8 spec: 1.0.0 +SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +YAJL
This is actually the version string of the binary on the host system:
❯ /usr/bin/crun --version crun version 1.0 commit: 139dc6971e2f1d931af520188763e984d6cdfbf8 spec: 1.0.0 +SYSTEMD +SELINUX +APPARMOR +CAP +SECCOMP +EBPF +CRIU +YAJL
With this nice little feature we now have insight into the version used – and as it was shown in the detailed write-up of the Azure security problem mentioned above, this insight can be crucial to identify and use the right CVEs to start lateral movement.
Of course this requires us to be able to load containers of our choice, and to have the right libraries inside the container. This example is simplified because I knew about the target system and there was a container available with everything I needed. For a more realistic attack container image, check out whoc.