
Architettura di Docker
Se state leggendo questo articolo, immagino sappiate già come funziona Docker sotto il cofano, ma facciamo un rapido ripasso dei concetti principali:
Docker Client
È la parte solitamente più usata dagli utenti: il client è la CLI del Docker daemon e serve a gestire container, immagini e registry.
Può essere usato anche per gestire le network e i volume usati dai container.
Alcuni comandi utilizzabili con il Docker client:
#AVVIA UN CONTAINER NGINX MAPPANDO LA PORTA 80 HOST-CONTAINER
docker run -d -p 80:80 nginx
#SCARICA L'IMMAGINE DOCKER DI UBUNTU 22.04
docker pull ubuntu:22.04
#MOSTRA LE IMMAGINI DOCKER PRESENTI NEL SISTEMA
docker images
#HELPER PER LE NETWORK DOCKER
docker network
#HELPER PER I VOLUME DOCKER
docker volume
Docker Daemon
Il Docker daemon è di fatto il cuore dell’ambiente Docker: esegue i container, scarica le immagini e gestisce tutte le network e i volume.
Potete usare il Docker daemon tramite un client, oppure — se volete complicarvi la vita — connettervi direttamente al Docker socket. Il daemon resta in ascolto delle richieste API.
Docker Registry
Il Docker registry è dove vengono conservate le immagini Docker.
Di default Docker cerca le immagini su Docker Hub, ma se configurato le immagini vengono scaricate dal registry impostato. Un registry può essere pubblico o privato con autenticazione.
Minacce all’ambiente Docker
Docker socket
Il Docker socket /var/run/docker.sock è il socket UNIX su cui Docker resta in ascolto, ed è di proprietà dell’utente root. È il principale punto d’accesso per la Docker API.
Esempio di minaccia:
docker run -d -v /var/run/docker.sock:/var/run/docker.sock <image_name>:<image_tag>
Un attaccante può inviare richieste al Docker daemon (in esecuzione come root sull’host Docker) per:
- avviare un container privilegiato ed evadere da esso;
- scaricare un’immagine pubblica con backdoor, eseguirla, entrarci ed evadere.
Esempio di attacco:
- find / -name docker.sock 2>/dev/null
- docker images
- docker run -it -v /:/host <image_name>:<image_tag> chroot /host bash
Il Docker socket può essere locale, come visto sopra, ma può anche essere esposto su internet se eseguite il vostro Docker daemon con questi flag:
-H tcp://0.0.0.0:XXX
In questo caso il Docker socket sarebbe esposto sulla porta specificata in XXX e accessibile da internet.
Container privilegiato
Di default i container Docker sono “unprivileged” e non possono, ad esempio, eseguire un Docker daemon all’interno di un container Docker.
Il flag --privileged assegna tutte le capabilities al container. Quando l’operatore esegue docker run --privileged, Docker abilita l’accesso a tutti i device dell’host e imposta alcune configurazioni in AppArmor o SELinux, dando al container quasi lo stesso accesso all’host dei processi che girano fuori dai container sull’host stesso.
Causa radice:
docker run -d --privileged <image_name>:<image_tag>
Un attaccante può montare il filesystem dell’host e usare chroot per accedervi.
Esempio di attacco:
- capsh --print
- fdisk -l
- mount /target/disk /mnt/
- chroot /mnt/ bash
Abuso delle capabilities
In generale, dovreste controllare le capabilities del container: se ne possiede una di queste, potreste riuscire a evaderne o a fare danni (lo sfruttamento è piuttosto lungo e diverso per ogni scenario):
CAP_SYS_ADMIN
CAP_SYS_PTRACE
CAP_SYS_MODULE
DAC_READ_SEARCH
DAC_OVERRIDE
CAP_SYS_RAWIO
CAP_SYSLOG
CAP_NET_RAW
CAP_NET_ADMIN
Potete controllare le capabilities attuali del container con:
capsh --print
Network condivisa
Altro esempio di minaccia:
docker run -d --network host <image_name>:<image_tag>
Un attaccante con un accesso iniziale al container vulnerabile può fare pivoting verso altri container. Se sono presenti container di management, ad esempio Portainer, l’attaccante può usarli per istanziare una macchina privilegiata utile per evadere.
Potete scoprire l’interfaccia della network Docker eseguendo:
ip addr
L’host di solito crea un’interfaccia che funge da gateway per la network Docker, generalmente usando il primo indirizzo IP del range. Di default il range IP per la network Docker è 172.17.0.0/16 e l’host avrà l’indirizzo 172.17.0.1. Se l’indirizzo IP del container è 172.17.0.1, si può concludere che il container condivide il namespace di rete dell’host.
Abuso dell’appartenenza al gruppo
I membri del gruppo Docker possono eseguire operazioni Docker. Questa opzione consente a tutti gli utenti non-root di usare Docker, assumendo che il Docker daemon sia in esecuzione come root. Tuttavia questa appartenenza può essere sfruttata per fare privilege escalation sull’host Docker.
Verificate se l’utente può eseguire operazioni Docker controllando il file /etc/group:
- docker images
- cat /etc/group | grep docker
Abuso di Containerd
Se il Containerd daemon è in esecuzione sull’host e l’utility ctr è installata, può essere sfruttata per attaccare l’host e ottenere privilegi di root.
L’utility ctr può essere usata da un altro utente sull’host, se disponibile.
L’attacco può inviare richieste al Containerd daemon in esecuzione con privilegi di root, avviare un container, montarvi il filesystem dell’host, poi usare chroot per ottenere accesso root dall’interno del container.
Abuso di runc
Se il runtime runc è installato sulla macchina e chiunque può eseguire il comando runc, può essere sfruttato per attaccare l’host e ottenere privilegi di root.
L’attaccante può inviare richieste al daemon runc in esecuzione con privilegi di root, avviare un container, montarvi il filesystem dell’host, usare chroot per ottenere accesso root dall’interno del container.
Abuso di tool di management
Se un tool di management, ad esempio Portainer, è in esecuzione con una password debole o è vulnerabile, questo può portare al takeover della macchina host.
Un attaccante può accedervi, avviare un container privilegiato e — dall’interno del nuovo container — fare chroot verso il filesystem dell’host.
Docker registry insicuro
Se un Docker registry privato non usa autenticazione, o l’autenticazione non è abbastanza robusta, può essere attaccato.
Un attaccante può interagire con il Docker registry, con la sua macchina, e scaricare immagini senza usare Docker.
Raccolta informazioni:
- curl http://<target_registry>:5000/v2/_catalog
- curl http://<target_registry>:5000/v2/<repository_name>/tags/list
- curl http://<target_registry>:5000/v2/<repository_name>/manifests/<tag>
- curl -s http://<target_registry>:5000/v2/<repository_name>/blobs/<blobSum_value> --output
<output_filename>.tar
Immagini fasulle (backdoor)
L’attaccante può scaricare l’immagine originale dal registry, capirne la struttura e crearne una nuova con una backdoor. Poi può sovrascrivere l’immagine sul Docker registry e aspettare che qualcuno/qualcosa la faccia il deploy.
L’attaccante può scaricare un’immagine multiuso dal registry, capirne la struttura e modificarla per fornirsi accesso shell dopo il deploy. Poi può sovrascrivere l’immagine sul Docker registry e aspettare che qualcuno/qualcosa la faccia il deploy. A questo punto, le tecniche descritte qui sopra possono essere usate per ottenere accesso root sull’host Docker.
Auditing e sicurezza su Docker
Auditare e mettere in sicurezza il Docker socket
- Controllate i permessi del socket UNIX: gli altri utenti non dovrebbero avere alcun accesso.
- Controllate il socket TCP e assicuratevi che sia in ascolto solo sull’interfaccia locale oppure che sia presente autenticazione.
Il socket TCP Docker di default non è protetto, es. tcp://<host_ip>:2375, quindi dobbiamo implementare:
- Autenticazione.
- Canale cifrato.
Come?
- Usando SSH.
- Usando TLS (porta TCP 2376).
Auditare il gruppo Docker
- Controllate i membri (/etc/group): chi non è membro non potrà accedere a Docker.
Auditare la configurazione Docker
- Controllate se sono consentiti registry insicuri (/etc/docker/daemon.json).
Strumenti per la sicurezza di Docker
- https://github.com/docker/docker-bench-security
- https://github.com/kost/dockscan
- https://github.com/genuinetools/amicontained
Mettere in sicurezza i container
Seccomp
Seccomp (Security Computing mode) è una feature del kernel Linux che funge da filtro delle syscall. Non è una sandbox, ma spesso viene usata insieme alle sandbox per bloccare syscall.
Seccomp-eBPF (modalità 2) è supportato da Docker per limitare le syscall dei container, riducendo di fatto la superficie di attacco. Il profilo Seccomp può essere definito come file JSON. Il profilo Seccomp di default di Docker è una whitelist che blocca 44 syscall.
Per saperne di più, consultate questo link.
AppArmor
AppArmor è un’estensione del kernel che confina i programmi a un insieme limitato di risorse. Lega gli attributi di controllo accessi ai programmi, non agli utenti.
Un articolo di approfondimento su AppArmor arriverà presto! (presto?)
User namespace remapping
Questa tecnica permette di mappare un utente del container su un ID utente inesistente sull’host.
Es. root nel container, UID = 0, stesso utente sull’host, UID = 27072; poiché l’utente non esiste, nessun privilegio!
Impatti:
- nessuna modalità privilegiata;
- nessun chroot tramite bind mount.
Per saperne di più: https://docs.docker.com/engine/security/userns-remap/
Modalità rootless
È possibile eseguire il Docker daemon in modalità rootless. Purtroppo ci sono alcuni limiti noti: https://docs.docker.com/engine/security/rootless/#known-limitations
Conclusioni
- I container Docker offrono un metodo sicuro per isolare le applicazioni dal sistema host sottostante, se non vengono usati male.
- È importante assicurarsi che tutte le immagini usate in Docker provengano da fonti fidate e vengano aggiornate regolarmente.
- Mettere in sicurezza il Docker daemon, le immagini dei container e i container stessi è un passo importante per garantire che le applicazioni in esecuzione su Docker siano sicure.
- Implementare controllo degli accessi basato sui ruoli e monitoraggio aiuta a garantire un uso sicuro di Docker.
- Non dimenticate la sicurezza dell’host!
Grazie a Antonio che mi ha aiutato a scrivere (quasi tutto!) questo post.