Kubernetes ist heutzutage ein essenzieller Bestandteil von modernen Webanwendungen. Die Verbreitung ist in den letzten Jahren rasant gestiegen und auch wir als Pentester bzw Red Teamer stolpern immer häufiger über auf Kubernetes betriebene Infrastrukturen.

Da ein selbst verwaltetes Cluster jedoch ein erhebliches Unterfangen ist, liegt es nahe, auf gemanagte Lösungen von Azure, AWS oder GCP zurückzugreifen. In diesem Blog geht es speziell um unsichere Standardeinstellungen in Azure Kubernetes Services (AKS) und wie Angreifer einfache Schwachstellen nutzen können, um potenziell das gesamte Cluster zu übernehmen.

Kubernetes Grundlagen

Begriff Erklärung
Container Ein einzelner Containerd- oder Docker-Container
Pod Eine ausführbare Einheit in Kubernetes. Muss mindestens einen Container enthalten, kann aber auch aus mehreren zusammengehörenden Containern bestehen
Namespace Ein Tool zur Organisation von Kubernetes-Ressourcen. Zugriffsberechtigungen können auf Cluster- oder auf Namespace-Ebene festgelegt werden.
Worker Eine VM oder Computer, der Pods ausführen kann.
Service Account Ein Kubernetes-Dienstkonto, das Pods den Zugriff auf die Kubernetes-API erlaubt.
Managed Identity Ein Azure Service Principal, der virtuellen Maschinen den Zugriff auf die Azure-API erlaubt.

Für diesen Artikel sind zwei grundlegende Themen relevant: die Aufteilung in Management- und Workernodes und Kubernetes-interne Service Accounts:

Die Control Plane ist für die Steuerung des gesamten Clusters zuständig. Sie entscheidet, auf welchem Worker neue Pods gestartet werden, ob ein Pod skaliert werden muss oder ob ein Worker offline ist. Zusätzlich wird auf der Control Plane die Kubernetes-API bereitgestellt. Über diese API wird das gesamte Cluster gesteuert (Secrets anlegen, Pods starten, Befehle in einem Container ausführen, etc.). Diese API wird durch JWTs geschützt und ist standardmäßig nicht Anonym (sprich, ohne Token) nutzbar.

Tools wie kubectl nutzen im Hintergrund ebenfalls diese API. So sieht beispielsweise der Request aus, um alle laufenden Pods im aktuellen Namespace anzuzeigen.

Copy to Clipboard

Wenn jetzt einem Pod Zugriff auf diese API gewährt werden soll, werden in Kubernetes sogenannte Service Accounts verwendet. Diese Konten haben im Cluster Berechtigungen zugewiesen, die ihnen erlauben, definierte API-Endpunkte zu nutzen. Wenn man z.B. eine CI/CD-Lösung wie ArgoCD betreibt, läuft diese selbst im Cluster als Pod. Damit ArgoCD die Berechtigungen hat, neue Pods zu starten, wird ein Service Account mit eben diesen Berechtigungen erstellt.

Das für den Zugriff benötigte JWT wird von Kubernetes selbst gemanagt und in den vorgesehenen Pod unter /var/run/secrets/kubernetes.io/serviceaccount/token gemountet. Dies geschieht standardmäßig mit jedem Pod, auch wenn der Service Account keine Berechtigungen hat. Das Token wird in den Pod gemounted, außer es wird explizit in der Konfiguration abgeschaltet. Das bedeutet im Umkehrschluss, dass man bei der Konfiguration der Pods explizit verhindern muss, dass dieser Mount durchgeführt wird.

Managed Identities in Azure

Ein hierzu relativ äquivalentes Feature von Azure sind die sogenannten Managed Identities (AWS und GCP bieten dieselbe Funktionalität nur unter anderem Namen an). Diese Managed Identities erlauben es, virtuellen Maschinen Berechtigungen gegenüber der Azure API (management.azure.com) zu definieren. Hiermit kann einer VM z.B. Zugriff auf einen Azure Storage Account gegeben werden, ohne Zugangsdaten auf der VM selber speichern zu müssen.

Eine Anwendung kann ein Access-Token anfragen, indem der intern verfügbare Metadaten-Service angesprochen wird. Unter der URL http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https://management.azure.com/ wird der hierfür benötigte Endpunkt bereitgestellt.

Wichtig ist der Metadata-Header. Dieser wird in der Anfrage vorausgesetzt, um das Ausnutzen von Server-Side Request-Forgery (SSRF)-Schwachstellen zum Erlangen des Tokens zu verhindern.

Copy to Clipboard

Mit diesem Access-Token kann dann ganz normal mit den Berechtigungen der Managed Identity auf die Azure-API zugegriffen werden.

 Die Kombination aus beiden Welten

Mit diesen Grundlagen können wir jetzt einen Schritt weiter gehen und uns gemanagte Kubernetes-Cluster in Azure anschauen.

Nach dem Erstellen, wohlgemerkt mit den Standard-Konfigurationen, begrüßt uns diese Übersicht:

Was bedeutet das jetzt effektiv? Die Management-API des erstellten Clusters ist frei im Internet erreichbar, weil standardmäßig kein IP-Allowlisting aktiviert ist und der Cluster nicht als Private Cluster erstellt wird. Dies ist sichtbar, wenn eine einfache Anfrage an die obenstehende URL geschickt wird:

Copy to Clipboard

Da wir kein Token besitzen, bekommen wir von der API Unauthorized als Antwort. Doch was ist, wenn es in dem Pod eine Schwachstelle gibt, die das Auslesen von beliebigen Dateien erlaubt (z.B. Remote Code Execution oder Local File Inclusion)? Vor allem Local File Inclusions kommen uns als Pentester häufiger unter.

Als Beispiel nehmen wir an, dass in dem Cluster eine CI/CD-Pipeline läuft, die das Auflisten von Build-Artefakten erlaubt.

Mit einem Klick auf die Datei wird der Inhalt der Datei angezeigt.

In diesem Beispiel ist die Implementierung fehlerhaft und es wird nicht überprüft, von wo die zu lesende Datei geöffnet wird (Der klassische Fall, wodurch eine Local File Inclusion möglich wird). Durch Manipulation der Request-Parameter können beliebige Dateien von der Festplatte gelesen werden.

Als Erinnerung: jeder Pod bekommt standardmäßig das Token für den cluster-internen API-Zugriff gemountet, auch wenn dieses Token gar nicht verwendet wird (/var/run/secrets/kubernetes.io/serviceaccount/token). Das heißt, dass mit unserer einfachen LFI dieses Token lesbar ist.

Normalerweise bräuchte man als Angreifer die Möglichkeit Code auf diesem System auszuführen, um intern mit der API zu sprechen. Die API unseres Clusters ist allerdings unter test-vqims8hi.hcp.westeurope.azmk8s.io aus dem Internet erreichbar, was ein Blick in das JWT nochmals bestätigt:

Copy to Clipboard

Das bedeutet im Umkehrschluss, dass wir mit diesem Token im Namen des Service-Accounts uns gegenüber der Kubernetes-API authentifizieren können.

Copy to Clipboard

Wenn dieser Service Account z.B. die Berechtigungen hat, Secrets im Cluster auszulesen, neue Pods zu starten oder Konfigurationen zu ändern, können wir dies aus dem Internet tun.

Eine Ebene Tiefer: Authentifizierung mit Workload Identities
Da auf einem Kubernetes-Worker jedoch üblicherweise unterschiedliche Pods laufen, die unterschiedlichen Berechtigungen in Azure haben, ist die Lösung über eine Managed Identity nicht mehr zielführend. Um bei dem Beispiel der CI/CD-Pipeline zu bleiben: Diese kann z.B. in einen Build-Manager unterteilt sein, der Zugriff auf eine Message-Queue und eine Datenbank braucht. Als zusätzliche Komponente haben wir den Builder, der Zugriff auf das Source-Repository braucht. Beide Komponenten können jedoch unabhängig voneinander auf demselben Worker laufen. Um dieses Problem zu lösen, gibt es sogenannte Workload Identities.

Bei der Verwendung von Workload Identities in Azure Kubernetes Service (AKS) wird der Authentifizierungsprozess durch eine Kombination aus Kubernetes-Service Accounts, Federated Identities und Azure Managed Identities realisiert. Dieser Mechanismus ermöglicht es Pods, sich mit den Berechtigungen einer Azure Managed Identity zu authentifizieren, ohne dass Zugangsdaten direkt auf der VM gespeichert werden. Die folgenden Schritte beschreiben den Ablauf der Authentifizierung im Detail:

  1. Service Account und Federated Identity Setup
    • Ein Kubernetes-Service Account wird erstellt und mit einer Federated Identity verknüpft. Diese Verknüpfung wird über Entra konfiguriert und definiert, welche Azure Managed Identity für den Service Account zuständig ist.
    • Kubernetes generiert ein JWT (JSON Web Token), das für den Service Account gültig ist. Dieses Token wird automatisch in den Pod gemountet, wobei der Pfad in der Umgebungsvariablen AZURE_FEDERATED_TOKEN_FILE gespeichert ist (z. B. /var/run/secrets/azure/tokens/azure-identity-token).
  2. Token-Abfrage und Client Assertion Login
    • Die Anwendung im Pod liest das JWT aus dem gemounteten Dateipfad. Dieses Token dient als Client Assertion im OAuth 2.0-Fluss.
    • Mit dem Token, der AZURE_TENANT_ID und AZURE_CLIENT_ID (aus Umgebungsvariablen wie /proc/self/environ abgerufen), sendet die Anwendung eine POST-Anfrage an https://login.microsoftonline.com//oauth2/v2.0/token.
    • Die Anfrage enthält Parameter wie:
      scope=https://management.azure.com/.default
      client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
      client_id=
      client_assertion=
      grant_type=client_credentials
    • Microsofts Authentifizierungsservice validiert das JWT und gibt ein Access Token für die Zielressource (z. B. Azure Management API) aus.
  1. Zugriff auf Azure-Ressourcen
    • Das erhaltene Access Token wird verwendet, um Anfragen an die Azure-API zu senden (z. B. https://management.azure.com//listClusterAdminCredential).
    • Da das Token direkt aus dem Pod stammt, ist keine direkte Code-Execution auf dem Worker Node erforderlich – nur der Zugriff auf das JWT im Dateisystem.

Warum ist dies ausnutzbar?

  • LFI-Exploitation: Wenn eine Schwachstelle wie Local File Inclusion (LFI) in der Anwendung existiert, kann ein Angreifer das JWT aus /var/run/secrets/azure/tokens/azure-identity-token extrahieren. Dieses Token ist äquivalent zu einem Client-Secret und ermöglicht die Erstellung eines Access Tokens für Azure-Ressourcen.
  • Keine Code-Execution nötig: Der Angriff funktioniert, weil das JWT bereits im Pod verfügbar ist und die Authentifizierung über Azures OAuth-Endpunkt erfolgt. Selbst ohne direkten Code-Execution auf dem Worker Node kann das Token genutzt werden, um sich bei Microsoft zu authentifizieren.
  • Fehlkonfigurationen: Standardmäßig wird das JWT automatisch gemountet, es sei denn, die Konfiguration explizit deaktiviert wird. Dies schafft ein Risiko, wenn Pods nicht streng isoliert sind.

Als Beispiel soll wieder die bereits bekannte CI/CD-Anwendung dienen. Wir beginnen damit, die aktuellen Umgebungsvariablen auszulesen. Diese stehen in /proc/self/environ.

Hier sind für uns folgende Variablen von Relevanz:

  • AZURE_TENANT_ID
  • AZURE_CLIENT_ID
  • AZURE_FEDERATED_TOKEN_FILE

Das benötigte Token können wir auslesen, indem wir den Wert von AZURE_FEDERATED_TOKEN_FILE über unsere LFI auslesen (in diesem Fall /var/run/secrets/azure/tokens/azure-identity-token).

Dieses Token ist äquivalent zum Client-Secret bzw. Passwort und kann im normalen Login bei login.microsoftonline.com verwendet werden. Als weitere Informationen brauchen wir nur die Client- und die Tenant-ID, welche wir bereits durch die Umgebungsvariablen bekommen haben.

Copy to Clipboard

Dieses Token kann jetzt mit den üblichen Microsoft Administrations-Tools oder direkt mit der Azure-API verwendet werden. In diesem Fall hatte die Managed Identity die Cluster Administrator Rolle, welche das Anfragen von Admin-Credentials erlaubt:

Copy to Clipboard

Gegenmaßnahmen

Um die Sicherheit von Kubernetes-Clustern und gemanagten Cloud-Infrastrukturen wie Azure AKS zu gewährleisten, sind folgende Maßnahmen entscheidend:

  1. Sicherung der Kubernetes-API
  • Stellen Sie sicher, dass AKS-Cluster standardmäßig als Private Cluster konfiguriert werden. Dies blockiert den öffentlichen Zugriff auf die Kubernetes-API und erfordert explizite Netzwerkregeln für den Zugriff.
  • Wenn private Cluster keine Lösung sind, schränken sie den Zugriff auf die Management Plane auf wenige, bekannte IP-Adressen ein.
  1. Sicherung von Service Accounts
  • Setzen Sie in Pod-Definitionen den Wert automountServiceAccountToken auf false, um das Token für die Kubernetes-API nicht automatisch zu mounten. Dies verhindert, dass über Schwachstellen (wie eine LFI) auf das Token zugegriffen werden kann.
  • Nutzen Sie Kubernetes Role-Based Access Control (RBAC), um Service Accounts nur die minimal notwendigen Berechtigungen zu gewähren. Vermeiden Sie z. B. die Freigabe von Secrets oder Pod-Management-Rechten, es sei denn, es ist explizit erforderlich.
  1. Regelmäßige Sicherheitsaudits und Updates
  • Testen Sie regelmäßig Ihre Kubernetes-Cluster und Azure-Infrastruktur auf Schwachstellen, insbesondere bei gemanagten Services wie AKS.
  • Die auf Kubernetes betriebene Anwendung sollte mindestens genauso regelmäßig getestet werden.

Fazit

Die Kombination aus unsicheren Standardeinstellungen in Azure Kubernetes Services (AKS) und der automatischen Bereitstellung von Service Account-Tokens oder Workload-Identity-JWTs schafft erhebliche Risiken für die Sicherheit von Clustern. Einfache (LFI)-Schwachstellen ermöglichen es Angreifern, sensible Tokens aus Pods zu extrahieren und diese zur Authentifizierung bei der Kubernetes-API oder Azure-Resources zu missbrauchen.

Sicherheit in modernen Umgebungen ist ein Gemeinschaftsprojekt und Sicherheit kann es nur geben, wenn alle Zahnräder ineinandergreifen: Das Ops-Team muss eine sichere Infrastruktur bereitstellen, die eine möglichst geringe Angriffsoberfläche bietet. Das Dev-Team muss nach Möglichkeit sicheren Code schreiben, damit keine ausnutzbaren Sicherheitslücken entstehen. Das Security-Team muss dann die entsprechenden Logs sammeln und funktionierendes Alerting haben, um Angriffe zu erkennen. In meiner Erfahrung als Pentester und Red-Teamer funktioniert dieses Zusammenspiel jedoch nur bei den wenigsten unserer Kunden. Sicherheit basiert häufig auf Annahmen. Dabei sind Sätze wie „Das ist ja die Aufgabe vom Security-Team“ nicht unüblich. Doch jeder trägt in seinem Aufgabengebiet zu einem sicheren Gesamtkonstrukt bei.