2026-03-15 | Pinperepette

Il Container Non E' una Cassaforte

Docker isola, ma non come pensi. Misconfiguration, CVE reali e kernel condiviso: cosa separa davvero un container da una VM. Lab pratico con 4 script.

Container Escape Docker Namespace Kernel

// La Cena

Sezione 00. L'antefatto

Sabato sera. Ristorante. Siamo in otto, amici e amici di amici. La iena e' di fronte a me, il menu e' arrivato, nessuno l'ha ancora aperto perche' si chiacchiera. Tutto tranquillo finche' qualcuno non menziona che gli hanno bucato il sito dell'azienda.

"Ma come e' possibile? Non era tutto in Docker?" dice uno.

E li' si accende il tipo. L'amico dell'amico. Non lo conosco, me l'hanno presentato mezz'ora fa. "Ah ma si', Docker. Io uso Docker per tutto. E' un sistema blindato, e' come una macchina virtuale. Gira tutto isolato, anche se ti bucano il container non possono fare niente al server."

Annuisco. Bevo un sorso di vino. La iena mi guarda. Sa gia' come finisce.

"Cioe' praticamente e' una sandbox, no?" insiste il tipo. Si e' girato verso di me perche' qualcuno gli ha detto che "faccio cose con i computer". "Una blackbox. Quello che succede dentro resta dentro."

"Mm."

"Ti giuro, da quando uso Docker dormo tranquillo. Metto tutto in container e via."

La iena mi mette una mano sul braccio. Il gesto universale per "no, ti prego, non farlo".

"Scusate un attimo. Vado a prendere una cosa in macchina."

Torno con l'M4 sotto il braccio. La iena chiude gli occhi. Il tipo mi guarda perplesso. Gli altri hanno gia' capito che la serata ha preso una brutta piega.

"Hai il telefono con l'hotspot? Collegami."

0
Per uscire dal container
0
Serve solo --privileged
0
Exploit necessari
0
Processi host visibili

// Cos'e' Davvero un Container

Sezione 01. Non e' quello che pensi

"Prima di rompere qualcosa, capiamo cosa stiamo rompendo."

"Un container Docker e' una macchina virtuale leggera, no?" dice il tipo.

"No. E' un processo con dei muri intorno. Ma i muri sono software, non hardware."

Silenzio. Gli altri hanno smesso di mangiare.

"Un container e' un processo Linux con namespace isolati, capability ridotte, syscall filtrate, filesystem separato e rete virtualizzata. Non e' un processo 'normale': ha protezioni reali. Ma gira sullo stesso kernel del sistema operativo host. Lo stesso kernel. E il kernel e' la superficie di attacco."

Un container Docker e' fatto di piu' strati di isolamento:

NS
Namespace
Isolamento reale: processi, rete, filesystem, hostname, utenti. Non puoi inviare segnali a processi fuori namespace
CG
Cgroup
Limita le RISORSE: CPU, RAM, I/O disco, banda di rete
SC
Seccomp
Filtra le syscall: blocca mount, ptrace, bpf e altre pericolose a livello kernel
CP
Capabilities
Root nel container ha meno poteri del root sull'host. Capability ridotte di default
OV
Overlay FS
Filesystem a strati: immagine base + layer modifiche, separato dall'host

"Aspetta, ma allora una macchina virtuale cos'e'?"

"Una VM ha il suo kernel. Un hypervisor (tipo VirtualBox o HyperKit) emula l'hardware e ogni VM ci gira sopra il suo sistema operativo completo. Il kernel della VM e' separato da quello dell'host. Per uscire da una VM devi trovare un bug nell'hypervisor. Ne escono anche per gli hypervisor: VENOM per QEMU, escape per VMware ESXi, VirtualBox, Xen. Niente e' invulnerabile. Ma la superficie di attacco e' molto piu' piccola rispetto a un kernel Linux intero. Per uscire da un container la superficie e' enorme: kernel, runC, Docker daemon. Oppure basta una misconfiguration."

Container

Condivide il kernel dell'host

Isolamento = namespace + seccomp + capabilities (software)

Escape = exploit kernel, bug runC, o misconfiguration

Un bug kernel buca TUTTI i container

Avvio in millisecondi, peso ~MB

Macchina Virtuale

Ha il proprio kernel

Isolamento = hypervisor (hardware)

Escape = exploit hypervisor (meno frequente, superficie ridotta)

Un bug kernel buca solo quella VM

Avvio in secondi, peso ~GB

"Ti faccio vedere." Apro il terminale.

$ docker run --rm alpine uname -a Linux e9b06c8d5edd 5.10.104-linuxkit #1 SMP x86_64 Linux $ docker run --rm ubuntu uname -a Linux b858ee8db5f9 5.10.104-linuxkit #1 SMP x86_64 GNU/Linux $ docker run --rm debian uname -a Linux decc1ef75bbf 5.10.104-linuxkit #1 SMP x86_64 GNU/Linux

"Vedi? Alpine, Ubuntu, Debian. Tre distribuzioni diverse. Stesso kernel: 5.10.104-linuxkit. Se domani esce una vulnerabilita' in quel kernel, tutti e tre i container sono vulnerabili. Tutti. Contemporaneamente."

Scrollo piu' giu'. Un container normale: 4 processi, la sua interfaccia di rete, 22 mount points. Un mondo a se'. Poi lo stesso container con --privileged --pid=host: 317 processi. /sbin/init, i thread del kernel, lo scheduler, tutto. I device dell'host: loop0, loop1, fuse, hwrng. Il tipo si sporge verso lo schermo.

Terminale: kernel condiviso tra distro, namespace normale vs privilegiato, 317 processi host

"Ma aspetta. Come fa un container a essere 'solo un processo'? Ha il suo filesystem, la sua rete..."

Apro un altro script. "Guarda qui. Lancio un sleep 30 in un container. Dentro, il processo crede di essere PID 1, l'init del sistema. In realta' sull'host e' il processo 59006. Stesso processo, due numeri diversi. Il namespace PID gli fa credere di essere solo. Poi guarda i cgroup: questo container ha 64 MB di RAM e mezzo core CPU. Sono i limiti che il kernel gli impone. Un guinzaglio."

Terminale: PID reale vs PID nel container, limiti cgroup, schema Docker container

Il tipo fissa lo schermo. "Ok, ma in pratica non puoi uscire dal container, no? Cioe', i namespace..."

"I namespace sono barriere del kernel. Ma le barriere del kernel si possono abbassare. A volte basta un flag."

Il punto chiave: un container offre isolamento reale, ma non allo stesso livello di una VM. Kubernetes, AWS, Google Cloud eseguono milioni di container multi-tenant. Se non isolassero nulla, sarebbe impossibile. Ma un container configurato male, o un kernel con una CVE non patchata, puo' trasformare quell'isolamento in un'illusione. La differenza tra "ragionevolmente sicuro" e "bucato" sta nella configurazione e nel patching.

// Misconfiguration 1: --privileged

Sezione 02. Non e' un exploit, e' peggio

"Vediamo cosa succede quando lanci un container con --privileged."

"Io non uso mai quel flag," dice il tipo.

"Sei sicuro? Hai mai seguito un tutorial su Stack Overflow che diceva 'se non funziona, aggiungi --privileged'? Hai mai configurato un container che deve accedere a un dispositivo USB, a una GPU, a una porta seriale? Il flag privileged e' la soluzione rapida che tutti usano quando qualcosa non va. E poi resta li'."

Container normale. Provo a vedere i dischi:

$ docker run --rm alpine fdisk -l (nessun output: il container non vede i dischi) $ docker run --rm alpine ls /dev/sda* ls: /dev/sda*: No such file or directory

Bene. Il container non vede niente. Adesso con --privileged:

$ docker run --rm --privileged alpine fdisk -l Disk /dev/vda: 60 GB, 63999836160 bytes, 124999680 sectors Device Boot StartCHS EndCHS StartLBA EndLBA Sectors Size /dev/vda1 1,0,1 618,63,32 2048 124999679 124997632 59.6G

"Vedi quel disco? 60 GB. E' il filesystem dell'host. Adesso lo monto."

$ docker run --rm --privileged alpine sh -c ' mkdir -p /mnt/host mount /dev/vda1 /mnt/host ls /mnt/host/' containerd docker kubeadm kubelet-plugins nfs swap

"Quello e' il filesystem dell'host. Posso leggere qualsiasi file. Posso scrivere qualsiasi file. Posso mettere una backdoor, modificare la configurazione, cancellare tutto. Un flag. Trenta secondi."

Giro lo schermo verso il tipo. In alto il container normale: fdisk -l non da' output, /dev/sda non esiste. Sotto, lo stesso container con --privileged: un disco da 60 GB, /dev/vda1, montato, con tutto il filesystem dell'host visibile. Containerd, docker, kubeadm, swap. Roba che non dovresti mai vedere da dentro un container.

Terminale: container normale vs privilegiato, mount del filesystem host

Il tipo non dice niente.

Onesta' intellettuale: questo non e' un exploit. E' una misconfiguration. --privileged e' documentato: disabilita esplicitamente la sicurezza del container. E' come dare chmod 777 / e poi lamentarsi che qualcuno legge i tuoi file. Il punto non e' che Docker e' insicuro. E' che la gente lo configura male senza sapere cosa sta facendo.

"Su Docker Desktop per Mac, l'host e' una VM LinuxKit, non macOS direttamente. Quindi qui stai vedendo la VM. Ma su un server Linux in produzione? Quello e' il vero host. Root access completo."

Perche' --privileged e' cosi' pericoloso? Rimuove tutte le restrizioni: da' al container tutte le capability del kernel (CAP_SYS_ADMIN, CAP_NET_ADMIN, tutte), monta procfs e sysfs in read-write, e rende visibili tutti i device dell'host. Il container diventa indistinguibile da un processo root sull'host.

01_privileged_escape.sh. Container escape via --privileged: fdisk, mount /dev/vda1, lettura filesystem host. Un flag, trenta secondi.

// Misconfiguration 2: docker.sock

Sezione 03. Le chiavi del regno (che gli dai tu)

"Ok, non usero' mai piu' --privileged," dice il tipo. "Ma io non lo uso."

"Bene. Hai un CI/CD? Jenkins? GitLab Runner? GitHub Actions self-hosted?"

"GitLab Runner, perche'?"

"Come fa il runner a creare container per i tuoi job?"

"..."

"Monta /var/run/docker.sock dentro il container del runner. Vero?"

Il docker socket e' il file attraverso cui qualsiasi programma parla con il demone Docker. Chi ha accesso a quel file controlla Docker. Puo' creare container, distruggerli, ispezionarli, e soprattutto: puo' creare container privilegiati con il filesystem dell'host montato.

"Guarda." Primo step: da dentro un container con il socket montato, parlo con il demone Docker.

container$ curl -s --unix-socket /var/run/docker.sock http://localhost/version {"Platform":{"Name":"Docker Desktop 4.7.0"},"Version":"20.10.14"...} container$ curl -s --unix-socket /var/run/docker.sock http://localhost/containers/json [{"Id":"643aeeb0593d","Names":["/great_liskov"],"Image":"alpine"...}]

"Vedo il demone Docker. Vedo i container attivi. E adesso: creo un nuovo container privilegiato che monta l'intero filesystem."

container$ docker run --rm --privileged -v /:/host alpine cat /host/etc/os-release PRETTY_NAME="Docker Desktop"

"Dal container originale, attraverso il socket, ho creato un secondo container privilegiato con accesso all'host."

Lo schermo parla chiaro. In alto il container che interroga il Docker daemon con curl: versione, lista dei container attivi, tutto esposto. In basso il container figlio che legge PRETTY_NAME="Docker Desktop" dal filesystem host. Due passaggi. Nessun exploit.

Terminale: escape via docker.sock, container crea container privilegiato

"Ma anche questo e' una misconfiguration, no?"

"Si'. E anche questa e' documentata: la stessa Docker dice 'whoever controls docker.sock controls the host'. Ma il punto e' che questa misconfiguration e' ovunque. Il runner CI/CD ha il socket montato per necessita' funzionale. Ma chiunque esegua codice in quel container — un test, una build, una dipendenza malevola da npm. Chiunque puo' parlare con il demone Docker e prendersi tutto. La misconfiguration diventa un vettore di attacco quando il codice che gira nel container non e' tutto tuo."

01
Container
Runner CI/CD con docker.sock montato
02
Socket
curl al Docker daemon via /var/run/docker.sock
03
Create
Nuovo container --privileged -v /:/host
04
Root
Accesso completo all'host

Script reference: 02_docker_sock_escape.sh. Dimostra entrambi gli step: comunicazione col demone e creazione di container privilegiato dall'interno.

02_docker_sock_escape.sh. Escape via docker.sock: comunicazione con il Docker daemon dall'interno del container, creazione di container privilegiato, accesso al filesystem host. Il pattern classico dei setup CI/CD.

// L'Illusione dei Namespace

Sezione 04. Vedere per credere

"Ma allora i namespace non servono a niente?" chiede il tipo.

"Servono eccome. Sono meccanismi reali del kernel: il PID namespace non ti fa solo 'non vedere' gli altri processi, ti impedisce di mandare segnali a processi fuori dal tuo namespace. Il network namespace ti da' uno stack di rete completamente separato. Sono barriere vere, non filtri cosmetici. Il problema e' che dipendono tutte dallo stesso kernel. E il kernel si puo' configurare male, oppure puo' avere bug."

Container normale: vede solo se stesso.

container$ ls /proc | grep -c "^[0-9]" 4 processi container$ ip addr show eth0 | grep "inet " inet 172.17.0.3/16 brd 172.17.255.255 scope global eth0 container$ hostname 85c7069c96a8

4 processi, il suo IP, il suo hostname. Sembra un mondo a se'. Adesso lo stesso container con --privileged --pid=host:

container$ ls /proc | grep -c "^[0-9]" 297 processi container$ ps aux | head -10 PID USER TIME COMMAND 1 root 0:04 /sbin/init 2 root 0:00 [kthreadd] 3 root 0:00 [rcu_gp] 11 root 0:00 [ksoftirqd/0] 12 root 0:03 [rcu_sched] 13 root 0:02 [migration/0]

"297 processi. /sbin/init, PID 1 dell'host. Thread del kernel. Scheduler. Tutto. L'isolamento c'era, e funzionava. Ma bastano due flag per rimuoverlo completamente. Il namespace e' una barriera reale del kernel, non un filtro cosmetico. Ma se dici al kernel 'disattivala', lui la disattiva."

"Ok, ma quei flag non li metti per sbaglio," dice il tipo, un po' sulla difensiva.

"No. Ma li metti quando qualcosa non funziona e Stack Overflow ti dice di metterli. E poi restano li'. Per sempre. Nessuno li toglie piu'."

Il dato che conta: container normale = 4 processi visibili. Container privilegiato = 297 processi visibili. Stessa macchina, stesso kernel. L'unica differenza e' un flag nella riga di comando.

Script reference: 03_namespace_demo.sh confronta la visibilita' di un container normale vs privilegiato, mostra il kernel condiviso tra distribuzioni diverse.

Script reference: 04_what_docker_really_is.sh smonta un container nei suoi componenti: processo con PID reale vs PID nel namespace, cgroup con limiti di risorse, overlay filesystem.

// L'Escape Elegante: CAP_SYS_ADMIN

Sezione 05. Il vero problema non e' --privileged

"Ok, ho capito: --privileged e docker.sock sono misconfiguration evidenti. Ma nella pratica, come si esce da un container senza quei flag?"

"Il vero problema non e' --privileged. Quello e' grossolano, si vede da un chilometro. Il problema sono le capability troppo potenti. In particolare una: CAP_SYS_ADMIN."

Nel kernel Linux, CAP_SYS_ADMIN permette operazioni molto potenti: mount, unshare, setns, manipolazione dei namespace, controllo di filesystem virtuali. Per questo molti security engineer la chiamano "the new root". Se un container ha questa capability (direttamente, o tramite configurazioni sbagliate in Kubernetes), puo' fare cose che non ti aspetti.

"Tipo?"

"Tipo entrare nel namespace dell'host passando da /proc."

Ogni processo Linux ha una directory in /proc/<PID>/ con i riferimenti ai suoi namespace:

1/proc/<PID>/ns/mnt # mount namespace
2/proc/<PID>/ns/pid # PID namespace
3/proc/<PID>/ns/net # network namespace
4/proc/<PID>/root # root filesystem del processo

Se il container puo' vedere i processi dell'host (ad esempio con hostPID: true in Kubernetes) e ha CAP_SYS_ADMIN, puo' fare setns() su uno di quei file ed entrare nel namespace di quel processo. Se il processo e' dell'host, sei nel namespace dell'host.

container$ nsenter --target 1 --mount root@host:/# host$ cat /etc/hostname production-server-01

"Sei nel mount namespace di PID 1. Il filesystem dell'host. Senza --privileged, senza docker.sock, senza exploit kernel."

C'e' anche una variante piu' semplice. Ogni processo ha /proc/<pid>/root, che e' la root filesystem di quel processo. Se puoi vedere un processo host:

container$ ls /proc/1/root/etc/ hostname hosts passwd shadow fstab ... container$ chroot /proc/1/root root@host:/#

"Stai guardando il filesystem dell'host attraverso /proc. Un chroot e ci sei dentro."

Lo schermo racconta tutta la storia. In alto, il container normale: 4 processi, /proc/1 e' se stesso. Al centro, con --cap-add SYS_ADMIN --pid=host: 318 processi, PID 1 e' /sbin/init dell'host. In fondo, nsenter prova a entrare nel mount namespace dell'host e viene bloccato dal profilo seccomp di Docker Desktop. Su un server in produzione con seccomp disabilitato, quel comando passa.

Terminale: CAP_SYS_ADMIN escape, /proc/1 host, nsenter bloccato da seccomp

Il tipo si passa una mano sulla faccia. "E Kubernetes permette queste cose?"

"Non di default. Per questo i Pod Security Standards di Kubernetes bloccano CAP_SYS_ADMIN, hostPID, hostNetwork e hostPath. Ma basta una policy troppo permissiva, un namespace senza restrizioni, un helm chart copiato da internet senza leggere i parametri."

Il punto: la sicurezza dei container dipende molto piu' dalle capability che da Docker stesso. CAP_SYS_ADMIN e' considerata quasi equivalente a root nel kernel Linux. Se il tuo container ce l'ha, l'isolamento e' nominale. Per questo la regola e' sempre --cap-drop ALL e poi aggiungi solo quello che serve.

// Le CVE: Bug Reali, Non Misconfiguration

Sezione 06. Quando non e' colpa tua

"Va bene," dice il tipo. "Non uso --privileged, non monto docker.sock, tolgo CAP_SYS_ADMIN. Adesso sono al sicuro?"

"Quasi. Finche' non esce la prossima CVE."

Una CVE (Common Vulnerabilities and Exposures) e' un bug di sicurezza catalogato. I container hanno una storia ricca di CVE che permettono l'escape senza nessun flag speciale:

CVE Anno Cosa Serve --privileged?
CVE-2022-0492 2022 Cgroup escape via release_agent. Il kernel non verificava i permessi quando un processo scriveva il file release_agent. Escape tramite unshare + mount cgroup No (basta CAP_SYS_ADMIN)
CVE-2022-0847 2022 Dirty Pipe. Permette a un processo non privilegiato di scrivere su file read-only. Se un file dell'host era montato nel container, poteva essere modificato. Non e' automaticamente escape, ma apre la strada No
CVE-2024-21626 2024 Bug in runC. File descriptor leak durante la creazione del container. Escape al filesystem host No
Docker API esposta ricorrente Docker Engine API esposta su TCP (porta 2375) senza autenticazione. Configurazione comune in ambienti mal gestiti. Chiunque in rete puo' creare container privilegiati No
CVE-2025-31133 2025 runC: symlink su /dev/null verso file procfs. Il container redirige le bind-mount e ottiene scrittura su file host No

"CVE reali, container escape reali. Tutte senza --privileged. Il caso piu' comune in assoluto: Docker Engine API esposta su TCP senza autenticazione. Il container fa una richiesta HTTP a un indirizzo interno e ottiene accesso completo al demone Docker. Nessun socket montato, nessun flag. Una GET."

"Pero' aspetta," dice il tipo. "Anche le VM hanno CVE. VENOM, gli escape di VMware..."

"Giusto. Niente e' invulnerabile, nemmeno gli hypervisor. La differenza non e' che i container hanno bug e le VM no. La differenza e' la superficie di attacco. Il kernel Linux ha decine di milioni di righe di codice e centinaia di syscall esposte al container. Un hypervisor tipo Firecracker ne ha centinaia di migliaia. Meno codice = meno bug = piu' difficile trovare la falla. Ma non impossibile."

Il punto reale: l'esistenza di una CVE non significa che un sistema "non isola". Altrimenti niente isola: ne' Linux, ne' Windows, ne' le VM. La sicurezza e' sempre una combinazione di isolamento, patching e hardening. I container offrono isolamento reale, ma su una superficie di attacco piu' ampia rispetto a un hypervisor e con un margine di errore di configurazione molto piu' alto.

// Docker sul Mac: la Matrioska

Sezione 07. L'architettura che nessuno spiega

"Ma allora il mio Mac e' a rischio?" chiede il tipo.

"No, e ti spiego perche'. Ma la ragione e' ironica."

Docker Desktop su Mac non esegue i container direttamente su macOS. Non puo': i container sono una tecnologia Linux (namespace, cgroup). macOS non ha niente di tutto questo. Quindi Docker Desktop fa girare una VM Linux sotto il cofano, una distribuzione minimale chiamata LinuxKit. I container girano dentro quella VM.

+--------------------------------------------------+ | macOS Host (il tuo Mac) | +--------------------------------------------------+ | HyperKit / Apple Hypervisor | <-- isolamento hardware +--------------------------------------------------+ | LinuxKit VM (Linux 5.10.104) | | +------+ +------+ +------+ | | | C1 | | C2 | | C3 | <-- i tuoi container | +------+ +------+ +------+ | | Docker Daemon | +--------------------------------------------------+

"Quando fai l'escape dal container, esci nella VM LinuxKit. Non su macOS. C'e' un hypervisor in mezzo, lo stesso tipo di isolamento hardware delle macchine virtuali. Quello si', e' difficile da bucare."

"Quindi il mio Mac e' protetto dall'hypervisor, non da Docker."

"Esatto. L'ironia e' che Docker sul Mac e' piu' sicuro che su Linux proprio perche' c'e' una VM in mezzo. Su un server Linux i container girano direttamente sull'host. Se esci dal container, sei sull'host. Niente VM, niente hypervisor, niente rete di sicurezza."

"Cioe' in produzione e' peggio che sul mio portatile."

"Molto peggio."

// Il Vero Problema: il Kernel Condiviso

Sezione 08. Dove sta andando l'industria

"Quindi il problema non e' Docker in se'. E' il kernel monolitico condiviso."

"Esatto. Docker fa il suo lavoro: namespace, cgroup, seccomp, capabilities. Sono meccanismi reali del kernel Linux e funzionano. Il problema e' che tutti i container condividono lo stesso kernel, e quel kernel ha decine di milioni di righe di codice con centinaia di syscall esposte. Ogni syscall e' un potenziale punto di ingresso."

"Per questo Kubernetes ha introdotto altri livelli di isolamento: Pod Security Standards, profili seccomp di default, rootless container e runtime sandbox come gVisor. L'ecosistema ha gia' reagito al problema. Docker da solo non basta, ma nessuno si aspetta che basti."

"E come si risolve il problema alla radice?"

"L'industria sta andando in una direzione precisa: container + microVM. Il meglio dei due mondi."

gV
gVisor
Google. Kernel user-space scritto in Go. Intercetta le syscall del container e le esegue in un sandbox. Non tocca il kernel host
KC
Kata Containers
Ogni container gira in una microVM con il suo kernel. Compatibile con Docker e Kubernetes
FC
Firecracker
Amazon (AWS Lambda). MicroVM che si avvia in 125ms. Isolamento hardware con leggerezza da container

"Firecracker e' quello che usa AWS per Lambda e Fargate. Ogni funzione gira in una microVM dedicata che si avvia in 125 millisecondi. Ha l'isolamento hardware di una VM con la velocita' di un container. E' il futuro."

"E perche' non lo usano tutti?"

"Perche' per la maggior parte dei casi d'uso, un container Docker ben configurato e' sufficiente. Non tutti hanno bisogno dell'isolamento multi-tenant di AWS. Ma se esegui codice non fidato (CI/CD, serverless, piattaforme SaaS), allora si', dovresti guardare queste soluzioni."

// Come Proteggersi Per Davvero

Sezione 09. Le regole

La iena sta parlando con le altre dall'altro lato del tavolo. Ci ha mollato da un pezzo. Gli altri ci guardano come se stessimo parlando in aramaico. Ma il tipo e' agganciato.

"Ok. Cosa devo fare?"

Regola Cosa fa Quanto conta
Mai --privileged Impedisce accesso diretto ai device e al filesystem host Critico
Mai montare docker.sock Impedisce al container di controllare il demone Docker Critico
--cap-drop ALL Rimuove tutte le capability Linux, aggiungi solo quelle necessarie Alto
--read-only Filesystem del container in sola lettura Alto
--security-opt no-new-privileges Impedisce escalation di privilegi dentro il container Alto
Seccomp profile Blocca le syscall pericolose (mount, ptrace, bpf) a livello kernel Medio-Alto
AppArmor / SELinux Policy di accesso fine-grained su file e operazioni Medio-Alto
User namespace remapping root nel container ≠ root sull'host Medio
Aggiornare Docker e il kernel Patcha le CVE. Ovvio, ma nessuno lo fa Critico

"Il principio e' semplice: dai al container il minimo indispensabile. Nessun privilegio che non sia strettamente necessario. E tratta il container come tratteresti qualsiasi processo che esegue codice potenzialmente ostile. Perche' e' esattamente quello che e'."

1# Container hardened: come dovresti lanciarlo
2docker run \
3 --cap-drop ALL \
4 --security-opt no-new-privileges \
5 --read-only \
6 --tmpfs /tmp \
7 --network none \
8 --user 1000:1000 \
9 myapp:latest

"Niente capability, niente privilege escalation, filesystem read-only, niente rete, utente non-root. Adesso si' che il container e' ragionevolmente isolato. Non invulnerabile: una CVE del kernel ti frega lo stesso. Ma ragionevolmente isolato."

// Il Ritorno in Macchina

Sezione 10. Epilogo

Chiudo il portatile. La iena mi guarda dal suo lato del tavolo con l'espressione di chi ha accumulato due ore di sopportazione silenziosa.

"Quindi Docker non serve a niente?" chiede il tipo.

"Docker e' fantastico. E i container isolano per davvero: namespace, seccomp, capabilities sono meccanismi reali del kernel. AWS, Google, Azure ci eseguono milioni di workload multi-tenant. Ma l'isolamento di un container non e' allo stesso livello di una VM, e una misconfiguration lo puo' azzerare in un secondo. Il problema non e' Docker. E' chi lo usa come se fosse una cassaforte senza leggere il manuale."

Silenzio.

"E quel flag --privileged nel tuo docker-compose?"

"Lo tolgo domani."

"Lo togli stasera."

Rimetto l'M4 nella borsa. La iena mi aspetta fuori dal ristorante, braccia incrociate.

"Sei contento adesso?"

"Si'."

"Bravo. Guido io."

I container sono progettati per isolare le applicazioni,
non gli attaccanti.

Lab: scripts/container-escape-lab. 5 script bash, zero dipendenze, solo Docker.