Introduzione: Perché la Sicurezza dei Container È Diventata Critica nel 2026
Se gestite infrastrutture Linux in produzione, è praticamente certo che Docker e Kubernetes facciano parte del vostro stack. I container hanno cambiato radicalmente il modo in cui facciamo deploy delle applicazioni — su questo siamo tutti d'accordo. Ma hanno anche aperto una superficie d'attacco completamente nuova, e nel 2026 gli attaccanti la stanno sfruttando come mai prima d'ora.
Il caso che ha fatto più rumore? VoidLink.
Si tratta di un framework malware sofisticato, scoperto a dicembre 2025, progettato specificamente per ambienti cloud e container Linux. Scritto in Zig, utilizza rootkit basati su eBPF e LD_PRELOAD, un sistema modulare con oltre 37 plugin, e la capacità di rilevare automaticamente il cloud provider su cui gira — AWS, GCP, Azure, Alibaba o Tencent. La cosa inquietante è che VoidLink non si limita a eludere gli strumenti di sicurezza: li profila attivamente e adatta il proprio comportamento di conseguenza.
E non è un caso isolato. Nel 2025, oltre il 65% delle violazioni di sicurezza dei container ha sfruttato privilegi root all'interno dei container stessi. Gli attacchi di container escape — dove l'attaccante esce dal container per compromettere l'host — sono diventati una minaccia concreta, con CVE critiche che dimostrano quanto sia sottile il confine tra isolamento containerizzato e sistema host.
In questo articolo andiamo a fondo sulle minacce attuali alla sicurezza dei container Linux e, cosa più importante, sulle strategie di hardening concrete che potete applicare oggi. Dalla configurazione di profili seccomp personalizzati all'implementazione di runtime sandboxed come gVisor e Kata Containers, passando per il monitoraggio runtime con strumenti eBPF — c'è tutto quello che serve per costruire una difesa in profondità che funzioni davvero.
VoidLink: Anatomia di un Malware Cloud-Native
Prima di parlare di difese, bisogna capire cosa stiamo affrontando. VoidLink rappresenta una nuova generazione di malware pensato specificamente per gli ambienti containerizzati e cloud-native. E, onestamente, è impressionante dal punto di vista tecnico (purtroppo).
Architettura e Meccanismo di Distribuzione
VoidLink utilizza un meccanismo di distribuzione a tre stadi, progettato per minimizzare la sua impronta su disco e sfuggire all'analisi statica:
- Stadio 1 — Loader iniziale: un binario minimale che stabilisce la persistenza e scarica il componente successivo in memoria
- Stadio 2 — Implant core: il cuore del framework, scritto in Zig, che gestisce la comunicazione C2 e il caricamento dei plugin
- Stadio 3 — Rootkit e plugin: moduli caricati dinamicamente per evasione, persistenza, movimento laterale e esfiltrazione dati
Il Sistema di Rootkit Adattivo
Una delle caratteristiche più insidiose di VoidLink è la selezione automatica del rootkit in base alla versione del kernel. In pratica, si adatta a qualsiasi ambiente trovi:
# Logica di selezione del rootkit (semplificata)
# VoidLink determina automaticamente quale tecnica di evasione utilizzare
Kernel < 4.0 → LD_PRELOAD hooking
- Intercetta le chiamate a funzioni di libreria (libc)
- Nasconde processi, file e connessioni di rete
- Funziona a livello userspace
Kernel 4.0 - 5.4 → Loadable Kernel Module (LKM)
- Modulo kernel compilato on-demand dal server C2
- Il server C2 compila il modulo per la versione kernel specifica del target
- Opera a livello kernel con pieno accesso
Kernel ≥ 5.5 con supporto eBPF → eBPF rootkit
- Attacca programmi eBPF ai tracepoint del kernel
- Filtra e manipola le informazioni visibili agli strumenti di monitoraggio
- Estremamente difficile da rilevare
La compilazione lato server dei moduli kernel è la parte che preoccupa di più: il server C2 riceve le informazioni sul kernel del target e compila rootkit personalizzati al volo. Non esiste una singola firma statica da cercare — ogni deployment è unico. Questo rende l'approccio tradizionale basato su firme sostanzialmente inutile.
Rilevamento dell'Ambiente Cloud e Container
VoidLink è progettato per essere consapevole del contesto in cui opera. Ecco come rileva l'ambiente circostante:
# Tecniche di rilevamento cloud utilizzate da VoidLink
# 1. Rilevamento cloud provider via metadata endpoint
curl -s http://169.254.169.254/latest/meta-data/ # AWS
curl -s http://metadata.google.internal/ # GCP
curl -s http://169.254.169.254/metadata/instance # Azure
# 2. Rilevamento container Docker
# Controlla l'esistenza di /.dockerenv
test -f /.dockerenv
# Controlla cgroup per indicatori Docker
grep -q "docker\|containerd" /proc/1/cgroup
# 3. Rilevamento Kubernetes
# Controlla variabili d'ambiente del service account
env | grep KUBERNETES_SERVICE
# Controlla mount del token
test -f /var/run/secrets/kubernetes.io/serviceaccount/token
Quando VoidLink rileva di trovarsi dentro un container Kubernetes, attiva automaticamente plugin specifici per la discovery dei servizi nel cluster, l'escalation dei privilegi tramite service account, e il movimento laterale verso altri pod. Insomma, sa esattamente dove si trova e cosa fare.
Evasione Attiva dei Prodotti di Sicurezza
VoidLink non si limita a evitare il rilevamento in modo passivo. Il malware profila attivamente i prodotti CDR, EDR e XDR installati — a livello di processo e di percorso — e adatta il proprio comportamento. Quando rileva strumenti di detection and response, modifica il timing del beacon C2 per ridurre la probabilità di essere individuato tramite analisi dei pattern. In parole povere: sa chi lo sta cercando e si nasconde di conseguenza.
Container Escape: Come gli Attaccanti Fuggono dall'Isolamento
L'isolamento dei container Linux si basa su tre meccanismi fondamentali del kernel: namespace, cgroup e capability. Quando uno di questi viene compromesso, l'attaccante può uscire dal container e prendere il controllo dell'host.
Vediamo i vettori di escape più comuni documentati nel 2025-2026.
1. Container Privilegiati
Eseguire un container con --privileged è, di fatto, equivalente a dare all'attaccante accesso diretto all'host. Eppure lo vedo ancora fare in produzione più spesso di quanto si vorrebbe ammettere:
# PERICOLOSO: container privilegiato
docker run --privileged -it ubuntu bash
# Un attaccante all'interno può montare il filesystem dell'host
mkdir /mnt/host
mount /dev/sda1 /mnt/host
# Accesso completo al filesystem dell'host
ls /mnt/host/etc/shadow
cat /mnt/host/etc/ssh/sshd_config
# Oppure caricare moduli kernel
insmod /path/to/malicious_module.ko
2. Socket Docker Esposto
Montare il socket Docker all'interno di un container è una pratica comune (soprattutto nei pipeline CI/CD) ma estremamente pericolosa:
# PERICOLOSO: montare il socket Docker
docker run -v /var/run/docker.sock:/var/run/docker.sock ubuntu
# Un attaccante può creare un container privilegiato dall'interno
docker run --privileged --pid=host -v /:/host ubuntu chroot /host
3. Capability Eccessive
Alcune capability Linux possono essere sfruttate per l'escape. La regola d'oro è: se non sapete esattamente perché vi serve una capability, non aggiungetela.
# CAP_SYS_ADMIN — la più pericolosa
# Permette mount, umount, e molte altre operazioni privilegiate
docker run --cap-add SYS_ADMIN -it ubuntu bash
# CAP_SYS_PTRACE — debug di processi
# Può essere usata per iniettare codice in processi dell'host
docker run --cap-add SYS_PTRACE --pid=host -it ubuntu bash
# CAP_NET_ADMIN — configurazione di rete
# Può manipolare regole di routing e firewall
docker run --cap-add NET_ADMIN --network=host -it ubuntu bash
4. Vulnerabilità del Kernel
Questo è il punto più critico, e spesso il meno considerato. Poiché tutti i container condividono lo stesso kernel dell'host, una vulnerabilità del kernel può essere sfruttata dall'interno di qualsiasi container per compromettere l'intero sistema. CVE critiche nel 2025 hanno dimostrato exploit di escape tramite vulnerabilità in namespace, cgroup e nel filesystem OverlayFS.
Hardening con Profili Seccomp: Filtrare le System Call
Seccomp (Secure Computing Mode) è un meccanismo del kernel Linux che restringe le system call disponibili per un processo. È la prima linea di difesa contro gli attacchi di container escape e l'esecuzione di codice malevolo — e, a mio avviso, una delle più sottovalutate.
Il Profilo Seccomp di Default di Docker
Docker include un profilo seccomp di default che blocca circa 44 system call su oltre 300 disponibili. Copre le syscall più pericolose, ma la superficie d'attacco rimane significativa:
# Verificare se seccomp è attivo nel container
docker run --rm alpine grep Seccomp /proc/1/status
# Output atteso:
# Seccomp: 2
# Seccomp_filters: 1
# ATTENZIONE: Kubernetes NON abilita seccomp di default!
# Dovete configurarlo esplicitamente
Quel dettaglio su Kubernetes è fondamentale e prende alla sprovvista molti team. Se state migrando da Docker a Kubernetes e pensate che seccomp sia "già attivo", vi sbagliate.
Creare un Profilo Seccomp Personalizzato
Il gold standard per la produzione è creare profili seccomp su misura per ogni workload. Il processo non è complicato, ma richiede attenzione.
Passo 1: Profilare le system call necessarie
# Usare strace per identificare le syscall utilizzate dalla vostra applicazione
strace -c -f -p $(pgrep my-app) -e trace=all 2>&1 | tail -30
# Oppure utilizzare il profilo seccomp di logging di Docker
# per registrare tutte le syscall utilizzate
docker run --security-opt seccomp=audit.json my-image
# Esempio con sysdig per monitorare le syscall di un container
sysdig -p "%evt.type" container.name=my-container | sort | uniq -c | sort -rn
Passo 2: Creare il profilo personalizzato
{
"defaultAction": "SCMP_ACT_ERRNO",
"defaultErrnoRet": 1,
"archMap": [
{
"architecture": "SCMP_ARCH_X86_64",
"subArchitectures": ["SCMP_ARCH_X86", "SCMP_ARCH_X32"]
}
],
"syscalls": [
{
"names": [
"read", "write", "close", "fstat", "lseek",
"mmap", "mprotect", "munmap", "brk",
"rt_sigaction", "rt_sigprocmask",
"ioctl", "access", "pipe", "select",
"sched_yield", "mremap", "msync",
"clone", "execve", "exit", "wait4",
"kill", "uname", "fcntl", "flock",
"fsync", "fdatasync", "getcwd",
"openat", "newfstatat", "readlinkat",
"socket", "connect", "accept",
"sendto", "recvfrom", "bind", "listen",
"epoll_create1", "epoll_ctl", "epoll_wait",
"getrandom", "futex", "clock_gettime",
"nanosleep", "exit_group"
],
"action": "SCMP_ACT_ALLOW"
},
{
"names": [
"mount", "umount2", "pivot_root",
"ptrace", "kexec_load", "reboot",
"init_module", "finit_module", "delete_module",
"io_uring_setup", "io_uring_enter", "io_uring_register",
"bpf", "perf_event_open",
"unshare", "setns"
],
"action": "SCMP_ACT_ERRNO",
"errnoRet": 1,
"comment": "System call pericolose: bloccate esplicitamente"
}
]
}
Notate come questo profilo blocchi esplicitamente io_uring_setup, bpf e ptrace — system call utilizzate sia dal rootkit Curing (trattato nel nostro articolo precedente sulla sicurezza di io_uring) sia dal framework VoidLink.
Passo 3: Applicare il profilo
# Docker
docker run --security-opt seccomp=my-seccomp-profile.json my-image
# Docker Compose
services:
my-app:
image: my-image
security_opt:
- seccomp:my-seccomp-profile.json
# Kubernetes — SecurityContext del Pod
apiVersion: v1
kind: Pod
metadata:
name: my-secure-pod
spec:
securityContext:
seccompProfile:
type: Localhost
localhostProfile: profiles/my-seccomp-profile.json
containers:
- name: my-container
image: my-image
AppArmor: Controllo d'Accesso Obbligatorio per Container
AppArmor è un modulo di sicurezza del kernel Linux che implementa il Mandatory Access Control (MAC). Mentre seccomp filtra le system call, AppArmor controlla l'accesso a file, directory, capability e risorse di rete a livello di path. Sono complementari, e usarli entrambi è fortemente consigliato.
Il Profilo Docker-Default
Docker genera automaticamente un profilo AppArmor chiamato docker-default per ogni container. Questo profilo fa il suo lavoro base:
- Impedisce la scrittura in
/proce/sys(con alcune eccezioni) - Impedisce il mount di filesystem
- Limita l'accesso a
/proc/sysrq-trigger - Blocca la modifica dei cgroup
Ma per la produzione, serve qualcosa di più mirato.
Creare un Profilo AppArmor Personalizzato per Container
# /etc/apparmor.d/containers/my-secure-container
#include <tunables/global>
profile my-secure-container flags=(attach_disconnected,mediate_deleted) {
#include <abstractions/base>
# Nega accesso a informazioni sensibili dell'host
deny /etc/shadow r,
deny /etc/passwd r,
deny /etc/ssh/** rwklx,
deny /root/.ssh/** rwklx,
# Nega accesso ai metadata cloud
deny network inet stream peer=(addr=169.254.169.254),
# Nega accesso al socket Docker
deny /var/run/docker.sock rw,
# Permetti accesso in lettura alle librerie di sistema
/lib/** r,
/usr/lib/** r,
/usr/local/lib/** r,
# Permetti accesso alla directory dell'applicazione
/app/** r,
/app/data/** rw,
/app/logs/** rw,
# Permetti operazioni di rete necessarie
network inet tcp,
network inet udp,
network inet6 tcp,
network inet6 udp,
# Nega mount di filesystem
deny mount,
deny umount,
deny pivot_root,
# Nega accesso a ptrace
deny ptrace (read, trace, attach),
# Nega caricamento di moduli kernel
deny capability sys_module,
deny capability sys_rawio,
deny capability sys_admin,
# Nega accesso ai dispositivi
deny /dev/** rw,
/dev/null rw,
/dev/zero r,
/dev/urandom r,
/dev/random r,
/dev/tty rw,
}
Per caricare e applicare il profilo:
# Caricare il profilo AppArmor
sudo apparmor_parser -r -W /etc/apparmor.d/containers/my-secure-container
# Verificare che il profilo sia caricato
sudo aa-status | grep my-secure-container
# Eseguire il container con il profilo personalizzato
docker run --security-opt apparmor=my-secure-container my-image
# In Kubernetes (se il nodo supporta AppArmor)
apiVersion: v1
kind: Pod
metadata:
name: my-secure-pod
annotations:
container.apparmor.security.beta.kubernetes.io/my-container: localhost/my-secure-container
spec:
containers:
- name: my-container
image: my-image
Un dettaglio cruciale che molti trascurano: il profilo blocca l'accesso all'endpoint dei metadata cloud (169.254.169.254). Questo è esattamente il vettore che VoidLink utilizza per rilevare il cloud provider e adattare il suo comportamento. Bloccandolo, eliminate un tassello fondamentale della fase di ricognizione del malware.
Hardening delle Capability Linux
Le capability Linux suddividono i tradizionali privilegi root in unità più granulari. Per i container, gestirle correttamente è essenziale per prevenire gli escape. E la buona notizia è che il principio è semplice: togli tutto, aggiungi solo il minimo indispensabile.
Configurazione di Sicurezza Minimale
# Docker: drop di TUTTE le capability, aggiunta solo di quelle necessarie
docker run \
--cap-drop ALL \
--cap-add NET_BIND_SERVICE \
--cap-add CHOWN \
--cap-add SETUID \
--cap-add SETGID \
--read-only \
--tmpfs /tmp:rw,noexec,nosuid \
--security-opt no-new-privileges:true \
my-image
# Kubernetes: SecurityContext completo
apiVersion: v1
kind: Pod
metadata:
name: hardened-pod
spec:
securityContext:
runAsNonRoot: true
runAsUser: 1000
runAsGroup: 1000
fsGroup: 1000
seccompProfile:
type: RuntimeDefault
containers:
- name: app
image: my-image
securityContext:
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop:
- ALL
add:
- NET_BIND_SERVICE
privileged: false
resources:
limits:
cpu: "500m"
memory: "256Mi"
requests:
cpu: "100m"
memory: "128Mi"
volumeMounts:
- name: tmp
mountPath: /tmp
- name: app-data
mountPath: /app/data
volumes:
- name: tmp
emptyDir:
medium: Memory
sizeLimit: "64Mi"
- name: app-data
emptyDir: {}
La flag no-new-privileges merita un'attenzione speciale: impedisce ai processi dentro il container di acquisire nuovi privilegi tramite binari setuid o setgid. È una di quelle opzioni che costa zero in termini di funzionalità ma blocca una delle tecniche di escalation più comuni.
Runtime Sandboxed: gVisor e Kata Containers
Tutte le misure di hardening che abbiamo visto finora — seccomp, AppArmor, capability — operano sullo stesso kernel dell'host. Se l'attaccante trova una vulnerabilità nel kernel, tutte queste protezioni possono essere aggirate simultaneamente. È un po' come mettere tre serrature sulla stessa porta.
Per questo, nel 2026 le organizzazioni che prendono sul serio la sicurezza stanno adottando runtime sandboxed che aggiungono un vero strato di isolamento aggiuntivo.
gVisor: Un Kernel in Userspace
gVisor implementa un kernel Linux in userspace chiamato Sentry che intercetta tutte le system call del container. Invece di passarle direttamente al kernel dell'host, Sentry le processa internamente, utilizzando solo circa 20 system call verso il kernel host (rispetto alle oltre 300 normalmente disponibili). La riduzione della superficie d'attacco è enorme.
# Installazione di gVisor (runsc)
# Scaricare e installare il runtime
curl -fsSL https://gvisor.dev/archive.key | sudo gpg --dearmor -o /usr/share/keyrings/gvisor-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/gvisor-archive-keyring.gpg] https://storage.googleapis.com/gvisor/releases release main" | sudo tee /etc/apt/sources.list.d/gvisor.list
sudo apt-get update && sudo apt-get install -y runsc
# Configurare Docker per utilizzare gVisor
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"runtimes": {
"runsc": {
"path": "/usr/bin/runsc",
"runtimeArgs": [
"--platform=systrap",
"--network=sandbox"
]
}
}
}
EOF
sudo systemctl restart docker
# Eseguire un container con gVisor
docker run --runtime=runsc -it ubuntu bash
# Verificare che gVisor è attivo
docker run --runtime=runsc alpine dmesg | head -5
# Output: "Starting gVisor..."
Vantaggi di gVisor:
- Riduce drasticamente la superficie d'attacco del kernel (da 300+ syscall a ~20)
- Non richiede hardware di virtualizzazione
- Avvio rapido, paragonabile a un container standard
- Protegge anche contro vulnerabilità kernel zero-day
Limitazioni da tenere a mente:
- Non supporta il 100% delle system call Linux (circa il 70% sono supportate)
- Overhead di prestazioni del 5-15% per workload I/O-intensive
- Alcune applicazioni che usano syscall esotiche potrebbero non funzionare correttamente
Kata Containers: MicroVM per Ogni Container
Kata Containers porta l'isolamento a un livello ancora superiore: ogni container viene eseguito all'interno della propria macchina virtuale leggera con un kernel dedicato. Sì, avete letto bene — un kernel separato per ogni container.
# Installazione di Kata Containers
# (esempio per Ubuntu 22.04+)
sudo snap install kata-containers --classic
# Configurare come runtime Docker
cat <<EOF | sudo tee /etc/docker/daemon.json
{
"runtimes": {
"kata-runtime": {
"path": "/snap/bin/kata-containers.runtime"
}
}
}
EOF
sudo systemctl restart docker
# Eseguire un container con Kata
docker run --runtime=kata-runtime -it ubuntu bash
# Verificare l'isolamento — il container ha il proprio kernel
docker run --runtime=kata-runtime alpine uname -r
# Output: kernel della microVM, diverso dall'host
# In Kubernetes con RuntimeClass
apiVersion: node.k8s.io/v1
kind: RuntimeClass
metadata:
name: kata
handler: kata-runtime
---
apiVersion: v1
kind: Pod
metadata:
name: kata-isolated-pod
spec:
runtimeClassName: kata
containers:
- name: app
image: my-sensitive-app
Vantaggi di Kata Containers:
- Isolamento hardware-enforced tramite virtualizzazione
- Anche se l'attaccante sfugge dal container, resta intrappolato nella microVM
- Kernel separato per ogni container — una vulnerabilità kernel nel container non compromette l'host
- Perfetto per workload multi-tenant e ambienti ad alta sicurezza
Limitazioni:
- Richiede hardware con supporto alla virtualizzazione (VT-x/AMD-V)
- Overhead di memoria più elevato (~5 MiB per microVM con Firecracker)
- Tempo di avvio leggermente superiore (~125ms con Firecracker, comunque molto veloce)
Quando Usare Quale Runtime
Per semplificare la scelta, ecco una matrice di confronto rapido:
# Matrice decisionale per la scelta del runtime
┌─────────────────────────────┬──────────┬──────────┬──────────────┐
│ Criterio │ runc │ gVisor │ Kata/ │
│ │(default) │ (runsc) │ Firecracker │
├─────────────────────────────┼──────────┼──────────┼──────────────┤
│ Isolamento │ Basso │ Alto │ Molto Alto │
│ Overhead prestazioni │ Minimo │ 5-15% │ 2-5% │
│ Overhead memoria │ Minimo │ Basso │ ~5 MiB/VM │
│ Tempo avvio │ <100ms │ <200ms │ ~125ms │
│ Compatibilità syscall │ 100% │ ~70% │ 100% │
│ Protezione kernel zero-day │ No │ Sì │ Sì │
│ Requisiti hardware │ Nessuno │ Nessuno │ VT-x/AMD-V │
├─────────────────────────────┼──────────┼──────────┼──────────────┤
│ Caso d'uso ideale │ Dev/Test │ Prod │ Multi-tenant │
│ │ │ generale │ Alta sicur. │
└─────────────────────────────┴──────────┴──────────┴──────────────┘
Isolamento di Rete e Protezione degli Endpoint Metadata
Un aspetto che viene sottovalutato troppo spesso nella sicurezza dei container è l'isolamento di rete. Per default, i container in un cluster Kubernetes possono comunicare liberamente tra loro e, potenzialmente, accedere a servizi esterni sensibili come gli endpoint metadata dei cloud provider. È praticamente un invito a nozze per il movimento laterale.
Network Policy Kubernetes
Le Network Policy di Kubernetes permettono di definire regole di firewall a livello di pod. Senza di esse, il cluster opera con una politica "allow-all" — esattamente ciò che un attaccante desidera.
# Politica di default: negare tutto il traffico ingress ed egress
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: default-deny-all
namespace: production
spec:
podSelector: {}
policyTypes:
- Ingress
- Egress
---
# Permettere solo il traffico necessario per l'applicazione
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: allow-app-traffic
namespace: production
spec:
podSelector:
matchLabels:
app: web-frontend
policyTypes:
- Ingress
- Egress
ingress:
- from:
- namespaceSelector:
matchLabels:
name: ingress-nginx
ports:
- protocol: TCP
port: 8080
egress:
- to:
- podSelector:
matchLabels:
app: api-backend
ports:
- protocol: TCP
port: 3000
- to: # Permettere DNS
- namespaceSelector: {}
podSelector:
matchLabels:
k8s-app: kube-dns
ports:
- protocol: UDP
port: 53
---
# CRITICO: Bloccare accesso ai metadata cloud
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
name: block-cloud-metadata
namespace: production
spec:
podSelector: {}
policyTypes:
- Egress
egress:
- to:
- ipBlock:
cidr: 0.0.0.0/0
except:
- 169.254.169.254/32 # Endpoint metadata AWS/Azure/GCP
Questa configurazione applica il principio del minimo privilegio alla rete. Ogni pod può comunicare solo con i servizi strettamente necessari, e l'accesso all'endpoint metadata cloud è bloccato — una difesa cruciale contro la fase di ricognizione di malware come VoidLink.
Egress Filtering per Contrastare le Comunicazioni C2
VoidLink supporta molteplici protocolli per le comunicazioni C2: HTTP/1.1, HTTP/2, WebSocket, DNS e ICMP. Per contrastare efficacemente queste comunicazioni, serve un filtraggio dell'egress davvero rigoroso:
# Esempio con Cilium Network Policy per egress filtering avanzato
apiVersion: cilium.io/v2
kind: CiliumNetworkPolicy
metadata:
name: strict-egress
namespace: production
spec:
endpointSelector:
matchLabels:
app: my-app
egress:
# Permettere solo HTTPS verso domini specifici
- toFQDNs:
- matchName: "api.example.com"
- matchName: "registry.example.com"
toPorts:
- ports:
- port: "443"
protocol: TCP
# Permettere DNS solo verso il DNS interno del cluster
- toEndpoints:
- matchLabels:
k8s-app: kube-dns
toPorts:
- ports:
- port: "53"
protocol: UDP
# Bloccare ICMP (usato da VoidLink per C2)
# Tutto il resto è negato implicitamente
L'uso di CiliumNetworkPolicy con toFQDNs è particolarmente efficace: permette di specificare i domini di destinazione consentiti anziché semplici IP. Questo impedisce agli attaccanti di usare domini dinamici per le comunicazioni C2 — una tecnica che framework come VoidLink impiegano regolarmente.
Monitoraggio Runtime con eBPF: Falco e Tetragon
L'hardening preventivo è fondamentale, ma da solo non basta. Servono strumenti di monitoraggio runtime capaci di rilevare e rispondere ad attività sospette in tempo reale. Nel 2026, la tecnologia eBPF è diventata lo standard de facto per questo tipo di monitoraggio — e per buone ragioni.
Falco: Rilevamento delle Anomalie
Falco, progetto CNCF ormai maturo, utilizza eBPF per monitorare le system call e rilevare comportamenti anomali in tempo reale. Ecco come installarlo e configurare regole specifiche per le minacce che abbiamo analizzato:
# Installazione di Falco
curl -fsSL https://falco.org/repo/falcosecurity-packages.asc | \
sudo gpg --dearmor -o /usr/share/keyrings/falco-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/falco-archive-keyring.gpg] https://download.falco.org/packages/deb stable main" | \
sudo tee /etc/apt/sources.list.d/falcosecurity.list
sudo apt-get update
sudo apt-get install -y falco
# Esempio di regole personalizzate per rilevare VoidLink
# /etc/falco/rules.d/voidlink-detection.yaml
- rule: Accesso al Metadata Cloud da Container
desc: Rileva tentativi di accesso all'endpoint metadata del cloud provider
condition: >
evt.type in (connect, sendto) and
container.id != host and
fd.sip = "169.254.169.254"
output: >
Tentativo di accesso al metadata cloud da container
(user=%user.name command=%proc.cmdline
container=%container.name image=%container.image.repository
connection=%fd.name)
priority: WARNING
tags: [network, container, cloud, mitre_discovery]
- rule: Caricamento Modulo Kernel da Container
desc: Rileva tentativi di caricare moduli kernel dall'interno di un container
condition: >
evt.type in (init_module, finit_module) and
container.id != host
output: >
Tentativo di caricamento modulo kernel da container
(user=%user.name command=%proc.cmdline
container=%container.name image=%container.image.repository)
priority: CRITICAL
tags: [process, container, mitre_persistence]
- rule: Uso di eBPF da Container Non Autorizzato
desc: Rileva l'uso della syscall bpf da container non autorizzati
condition: >
evt.type = bpf and
container.id != host and
not container.image.repository in (falco, tetragon, cilium)
output: >
Chiamata bpf da container non autorizzato
(user=%user.name command=%proc.cmdline
container=%container.name image=%container.image.repository)
priority: CRITICAL
tags: [process, container, mitre_defense_evasion]
- rule: Rilevamento LD_PRELOAD Sospetto
desc: Rileva l'impostazione di LD_PRELOAD che potrebbe indicare hooking
condition: >
evt.type = execve and
container.id != host and
evt.arg.environment contains "LD_PRELOAD"
output: >
Variabile LD_PRELOAD rilevata in esecuzione container
(user=%user.name command=%proc.cmdline
container=%container.name
env=%evt.arg.environment)
priority: WARNING
tags: [process, container, mitre_defense_evasion]
Tetragon: Enforcement in Tempo Reale
Falco è eccellente per il rilevamento, ma Tetragon (di Cilium/Isovalent) fa un passo in più: aggiunge la capacità di enforcement, bloccando le azioni sospette direttamente nel kernel senza latenza percepibile. Se Falco è l'allarme, Tetragon è la porta blindata che si chiude automaticamente.
# Installazione di Tetragon in Kubernetes via Helm
helm repo add cilium https://helm.cilium.io
helm repo update
helm install tetragon cilium/tetragon -n kube-system
# TracingPolicy per bloccare container escape
apiVersion: cilium.io/v1alpha1
kind: TracingPolicy
metadata:
name: block-container-escape
spec:
kprobes:
# Blocca tentativi di mount da container
- call: "__x64_sys_mount"
syscall: true
selectors:
- matchNamespaces:
- namespace: Mnt
operator: NotIn
values:
- "host_mnt_ns"
matchActions:
- action: Sigkill
argError: -1
# Blocca caricamento moduli kernel da container
- call: "__x64_sys_init_module"
syscall: true
selectors:
- matchNamespaces:
- namespace: Pid
operator: NotIn
values:
- "host_pid_ns"
matchActions:
- action: Sigkill
# Blocca uso di io_uring da container
- call: "__x64_sys_io_uring_setup"
syscall: true
selectors:
- matchNamespaces:
- namespace: Pid
operator: NotIn
values:
- "host_pid_ns"
matchActions:
- action: Sigkill
# Monitora uso di bpf da container
- call: "__x64_sys_bpf"
syscall: true
selectors:
- matchNamespaces:
- namespace: Pid
operator: NotIn
values:
- "host_pid_ns"
matchActions:
- action: Post
rateLimit: "1m"
La combinazione Falco + Tetragon è, a mio parere, la scelta migliore al momento: Falco per la rilevazione con regole flessibili e alerting granulare, Tetragon per l'enforcement in tempo reale con overhead sotto l'1%. Insieme coprono sia la detection che la response.
Difesa in Profondità: Checklist Operativa Completa
Bene, abbiamo coperto parecchio terreno. Ecco una checklist che riassume tutte le misure di hardening trattate. Usatela come riferimento pratico per valutare e migliorare la sicurezza dei vostri ambienti containerizzati.
1. Configurazione Base del Container
- Mai utilizzare container privilegiati (
--privileged) in produzione - Drop di tutte le capability con
--cap-drop ALL, aggiungere solo quelle necessarie - Eseguire come utente non-root (
runAsNonRoot: true) - Filesystem root in sola lettura (
readOnlyRootFilesystem: true) - Bloccare l'escalation dei privilegi (
allowPrivilegeEscalation: false) - Mai montare il socket Docker all'interno dei container
- Impostare limiti di risorse (CPU, memoria) per prevenire DoS
2. Profili di Sicurezza
- Applicare profili seccomp personalizzati per ogni workload
- Configurare profili AppArmor o SELinux dedicati
- Bloccare esplicitamente
io_uring,bpf,ptrace,mountnei profili seccomp - In Kubernetes, abilitare seccomp esplicitamente (non è attivo di default!)
3. Isolamento di Rete
- Utilizzare Network Policy in Kubernetes per segmentare il traffico tra pod
- Bloccare l'accesso agli endpoint metadata cloud (169.254.169.254) da container non autorizzati
- Non utilizzare
--network=hostsalvo necessità assoluta - Implementare service mesh (Istio, Cilium) per mTLS tra servizi
4. Immagini e Supply Chain
- Utilizzare immagini base minimali (distroless, scratch, Alpine)
- Scansione delle vulnerabilità con Trivy, Grype o Snyk
- Firmare le immagini con Cosign/Sigstore
- Implementare policy di ammissione (OPA Gatekeeper, Kyverno) per impedire il deploy di immagini non verificate
5. Runtime e Monitoraggio
- Valutare gVisor o Kata Containers per workload ad alto rischio
- Deployare Falco per il rilevamento delle anomalie runtime
- Deployare Tetragon per l'enforcement in tempo reale
- Configurare alerting per tentativi di container escape
- Monitorare l'uso di LD_PRELOAD, BPF e io_uring all'interno dei container
6. Difese Specifiche Anti-VoidLink
- Bloccare l'accesso ai metadata endpoint cloud tramite AppArmor e Network Policy
- Monitorare la creazione di programmi eBPF con Falco/Tetragon
- Proibire il caricamento di moduli kernel da container
- Rilevare e bloccare tentativi di LD_PRELOAD injection
- Implementare egress filtering rigoroso per limitare le comunicazioni C2
- Monitorare pattern di beacon anomali nel traffico di rete in uscita
Conclusioni: La Sicurezza dei Container È un Processo, Non un Progetto
La scoperta di VoidLink a dicembre 2025 ha reso evidente una realtà scomoda: gli attaccanti si stanno specializzando negli ambienti cloud-native con la stessa velocità con cui le organizzazioni li adottano. Il malware moderno non si limita a sfruttare vulnerabilità singole — è progettato per operare nativamente nell'ecosistema container, con capacità di rilevamento ambientale, evasione adattiva e modularità che ricordano i framework offensivi professionali.
La buona notizia? Abbiamo gli strumenti per difenderci, e sono maturi.
La combinazione di seccomp personalizzato, AppArmor, capability minimali, runtime sandboxed e monitoraggio eBPF crea una difesa in profondità che rende la vita estremamente difficile a qualsiasi attaccante — anche uno sofisticato come VoidLink.
Il punto chiave è non affidarsi mai a un singolo strato di protezione. Come abbiamo visto nel nostro articolo precedente sulle vulnerabilità di io_uring, gli attaccanti trovano costantemente modi per aggirare le difese individuali. La vera sicurezza sta nella sovrapposizione di livelli indipendenti, dove la compromissione di uno non invalida gli altri.
Il mio consiglio? Iniziate oggi. Revisionate i vostri deployment con la checklist che abbiamo visto, implementate subito le misure più critiche (seccomp, capability minimali, utente non-root) e pianificate l'adozione di runtime sandboxed e monitoraggio eBPF per i workload più sensibili. La sicurezza dei container non è un progetto con una data di fine — è un processo continuo, e il momento migliore per iniziare è adesso.