Toolkit para abusar de la API del kubelet en clústeres Kubernetes/k3s cuando
un ServiceAccount tiene el permiso RBAC get nodes/proxy.
get nodes/proxy parece "solo lectura del nodo", pero el kubelet deriva el
verbo de autorización del método HTTP:
| Petición al kubelet | Verbo RBAC | ¿Cubierto por get nodes/proxy? |
|---|---|---|
GET /pods, GET /logs/... |
get |
✅ |
POST /exec/..., POST /run/... |
create |
❌ |
GET /exec/... (upgrade) |
get |
✅ ← ejecución de comandos |
Es decir: con solo get nodes/proxy puedes ejecutar comandos en cualquier
contenedor del nodo vía GET /exec. Si hay un pod privilegiado que monta el
/ del host (típico: node-exporter, agentes de logging/monitoring), eso es
root en el host.
- El proxy del apiserver (
/api/v1/nodes/<n>/proxy/exec/...) descarta la query string en peticiones de upgrade (tryUpgradeno copiaRawQuery) → hay que hablar directo con el kubelet (:10250). - El kubelet suele rechazar WebSocket para exec pero acepta SPDY/3.1.
- Parámetros de exec:
command,output=1,error=1,input=1(valores"1"), nostdin/stdout/stderr. El upgrade exigeX-Stream-Protocol-Version.
Esta herramienta implementa un cliente SPDY/3.1 mínimo en Python puro (sin dependencias) que hace todo eso.
El token normalmente es el del ServiceAccount montado en un pod comprometido:
/var/run/secrets/kubernetes.io/serviceaccount/token (valor por defecto de
--token-file). El kubelet escucha en *:10250, así que --host es la IP del
nodo (alcanzable desde cualquier pod o desde el propio host).
# 1. Recon: ver todos los pods del nodo
python3 kubelet_pwn.py pods --host 10.129.60.174 --token-file token
# 2. Encontrar el camino a root (privileged / host mounts) — resuelve el mountPath
python3 kubelet_pwn.py priv --host 10.129.60.174 --token-file token
# 3. Ejecutar en el pod privilegiado que monta el host / en /host/root
python3 kubelet_pwn.py exec --host 10.129.60.174 --token-file token \
-n monitoring -p prometheus-node-exporter-xxxxx -c node-exporter \
-- /bin/sh -c 'id; cat /host/root/root/root.txt'priv ya te imprime el comando exec exacto listo para copiar.
- Si el contenedor objetivo no tiene
/bin/sh(imágenes distroless), ejecuta un binario suelto:-- /bin/cat /host/root/root/root.txt. --via-apiserversirve para los recon GET (pods/priv) si no llegas al kubelet directo, pero no funciona paraexec(el apiserver pierde la query).- Sin dependencias externas: solo Python 3 estándar (
zlib,ssl,socket).
Solo para pruebas de seguridad autorizadas (CTF, laboratorios, pentest con permiso explícito). El uso contra sistemas sin autorización es ilegal.
Destilado del privesc de la máquina HTB Fireflow: Langflow RCE → reuso de
credencial → MCP JWT alg=none → code-exec en pod → SA con get nodes/proxy →
exec SPDY/3.1 en node-exporter privilegiado → root.