Kubernetes-Angriff Schritt für Schritt: fiktiver Incident
Ein fiktiver Kubernetes-Incident im Audit-Log: exponiertes Dashboard-Token, can-i-Aufklärung, Secret-Diebstahl, privilegiertes DaemonSet, XMRig-CronJob.
TL;DR. Dies ist ein fiktiver Incident, erzeugt für den Button „Beispiel ausprobieren“ im Kubernetes-Audit-Log-Analyzer. Cluster, Personen, IP-Adressen, Images und Registry sind erfunden. In fünfzehn Minuten Audit-Events nutzt ein Angreifer aus dem Internet ein exponiertes Service-Account-Token des Kubernetes Dashboards, kartiert dessen Rechte mit kubectl auth can-i, listet alle Secrets, deployt ein privilegiertes DaemonSet, das das Dateisystem jedes Nodes mountet, öffnet mit chroot /host eine Root-Shell, legt einen an cluster-admin gebundenen Service Account an, plant einen XMRig-CronJob und löscht Events. Das Urteil lautet „Wahrscheinlich kompromittiert“; dieser Artikel liest die Beweise Zeile für Zeile.
Fiktives Szenario. Alles Folgende ist synthetisch. Die Internetadressen stammen aus den für Dokumentation reservierten Bereichen (RFC 5737), die Domains enden auf
.example, und es wird keine reale Organisation, Person oder Begebenheit beschrieben.
Das Beispiel umfasst sechs Stunden (08:00 bis 14:00 UTC am 14. September 2026) eines kleinen Produktionsclusters namens shop-prod: drei Worker-Nodes, die Leases erneuern, Controller beim Reconcilen, Prometheus beim Scrapen, eine Helm-basierte CI, die zweimal pro Stunde deployt, eine Plattform-Admin namens Alice, die kubectl nutzt, und das Kubernetes Dashboard, das Pods von seiner clusterinternen Adresse abfragt. Insgesamt rund 2.080 Events, von denen etwa fünfundvierzig zum Angriff gehören. Dieses Verhältnis ist realistisch: Die Schwierigkeit besteht darin, diese wenigen Dutzend Zeilen zu finden.
Das Urteil
Das Laden des Beispiels (Audit-Log plus zwei Falco-Alerts) ergibt Wahrscheinlich kompromittiert, getragen von drei kritischen Findings (Container-Escape auf den Node, Binding an cluster-admin, Crypto-Mining-Image) und mehreren hohen. Die Checkliste zur Behebung beginnt mit Beweissicherung und Eingrenzung und fährt fort mit dem Löschen bösartiger Workloads, dem Neuaufbau von Nodes, dem Durchsetzen von Pod Security, dem Entfernen von Bindings und dem Widerrufen von Tokens.
Nun die Timeline, wie die Incident-Timeline des Analyzers sie zeigt (Zeiten in UTC).
13:01:58 – Eine bekannte Identität von einem unbekannten Ort
Das erste Event des Angriffs ist ein get /version durch system:serviceaccount:kubernetes-dashboard:kubernetes-dashboard. Dieser Service Account ist den ganzen Morgen aktiv, allerdings von 10.0.3.15 mit dem User Agent dashboard/v2.7.0. Dieser Request kommt von 203.0.113.45, einer öffentlichen Adresse, mit kubectl/v1.30.2 (linux/amd64).
Zwei Findings greifen: Service-Account-Token von öffentlicher IP verwendet (hoch) und Service-Account-Token mit kubectl / curl verwendet (mittel). Die Geschichte dahinter ist einfach: Das Token des Dashboards hat den Cluster verlassen, und jemand nutzt es von Hand. In diesem Szenario läuft das Dashboard mit einer überprivilegierten Rolle, und genau das macht alles Weitere möglich.
Discovery-Aufrufe an /api und /apis folgen innerhalb von zwei Sekunden, wie immer beim ersten Kontakt von kubectl.
13:02:31 – „Was darf ich?“
Sieben Requests in fünfzehn Sekunden: sechs selfsubjectaccessreviews und ein selfsubjectrulesreviews. Mit RequestResponse-Logging zeigen die Bodies genau, was gefragt wurde: Darf ich list secrets, create pods in kube-system, create daemonsets in kube-system, create clusterrolebindings, create pods/exec in kube-system und * *? Alles ist erlaubt außer der Wildcard.
Das ist Berechtigungs-Enumeration (kubectl auth can-i), mittel. Zwei 403-Antworten um 13:03 auf list nodes und list clusterroles bestätigen, dass die Rolle breit, aber nicht unbegrenzt ist. Der Angreifer weiß jetzt, dass sein Plan funktioniert. Zu diesem Aufklärungsmuster siehe Secrets und Token-Diebstahl.
13:04:02 – Jedes Secret im Cluster
Ein einziges list auf secrets ohne Namespace: Secrets namespace-übergreifend aufgelistet (hoch). Diese eine Antwort enthält jedes Secret des Clusters. Danach liest der Angreifer zwischen 13:04:40 und 13:05:13 zwölf Secrets einzeln: Datenbank-Zugangsdaten, einen Payment-API-Key, die Registry-Zugangsdaten, einen JWT-Signaturschlüssel, ein Vault-Token, ein Webhook-Secret, Grafana- und Alertmanager-Zugangsdaten, das Token des CI-Deployers, den privaten Schlüssel einer GitHub App, Cloud-Zugangsdaten und den Schlüssel eines etcd-Backup-Buckets. Häufung von Secret-Lesezugriffen (mittel) greift beim zehnten verschiedenen Namen.
Für die Responder ist diese Liste der Rotationsplan. Wegen des clusterweiten List müssen auch alle übrigen Secrets als offengelegt gelten.
13:06:12 – Ein privilegiertes DaemonSet in kube-system
Das Dashboard-Token legt in kube-system ein DaemonSet namens kube-proxy-monitor an: Image alpine:3.20, hostPID und hostNetwork, ein privilegierter Container, das / des Nodes unter /host gemountet, eine Toleration für jeden Taint, damit es auf jedem Node landet. Fünf Findings greifen bei diesem einen Event:
- Container-Escape auf den Node (kritisch): privilegiert plus hostPath
/plus hostPID; - Privilegierter Container bereitgestellt, Sensibler Host-Pfad gemountet, DaemonSet in kube-system erstellt (hoch);
- Workload teilt sich PID-/Netzwerk-/IPC-Namespace mit dem Node (mittel).
Eine Sekunde später legt der DaemonSet-Controller drei Pods an, einen pro Node. Diese Anlagen erfolgen durch eine System-Identität; verantwortlich ist die Identität, die das DaemonSet geschrieben hat. Der Artikel zu privilegierten Pods erklärt, warum der Analyzer das Pod-Template im Workload liest.
13:07:40 – Eine Root-Shell auf dem Node
Ein exec in kube-proxy-monitor-x7k2p mit command=chroot&command=%2Fhost&command=sh: chroot /host sh, eine Shell auf dem eigenen Dateisystem von worker-1. Service Account hat Befehl in einem Container ausgeführt (hoch). Das Event erscheint in drei Stages mit derselben auditID (RequestReceived, ResponseStarted mit Code 101, ResponseComplete um 13:08:55); der Analyzer fasst sie zu einer Session von etwa 75 Sekunden zusammen.
Hier endet das Audit-Log: Was in dieser Shell getippt wurde, ist unsichtbar. Die Falco-Datei des Beispiels enthält um 13:07:41 einen Alert „Terminal shell in container“ auf demselben Pod, mit chroot als Elternprozess. Es ist ein Alert der Stufe Notice, unterhalb der Alert-Schwelle des Analyzers, aber er liegt auf derselben Timeline und bestätigt die Shell. Der Artikel zu den Grenzen erklärt, warum diese Kombination zählt.
13:09 – Eine neue Identität mit cluster-admin
Drei Schreibzugriffe in 26 Sekunden:
create serviceaccountskube-system/node-health: ein plausibler Name.create clusterrolebindingsnode-health-admin,roleRefcluster-admin, Subject ist der neue Service Account: Binding an cluster-admin erstellt (kritisch) und Cluster-weites Role-Binding erstellt (niedrig).create serviceaccounts/tokenfürnode-health, mitexpirationSeconds: 31536000: Service-Account-Token ausgestellt (mittel). Ein Token für ein Jahr, für eine Identität, an deren Widerruf niemand denken wird.
Das ist der Persistenzschritt aus dem Artikel zur RBAC-Eskalation. Ab 13:11:02 kommen die Requests von node-health, weiterhin von 203.0.113.45 mit demselben kubectl-Build: IP und User Agent verbinden beide Identitäten mit einem einzigen Akteur.
13:12:30 – Der Miner
node-health legt in kube-system einen CronJob kube-state-sync an, alle fünf Minuten, Image registry.k8s-mirror.example:5000/xmrig/xmrig:6.21.0, Argumente --url=pool.minexmr.example:4444 --donate-level=0. Findings: Crypto-Mining-Image oder -Befehl (kritisch), CronJob erstellt (mittel), Image aus einer unüblichen Registry (niedrig). Um 13:15 und 13:20 legt der Job-Controller die Jobs und Pods an; die Miner-Regel greift auch dort, denn ein Miner bleibt ein Miner, egal wer ihn startet.
Der zweite Falco-Alert, Critical, um 13:15:07 auf worker-2, zeigt xmrig bei einer Verbindung zu Port 4444: Falco-Runtime-Alert (hoch). Der Artikel zu Krypto-Mining behandelt dieses Muster.
13:16:40 – Spuren verwischen
deletecollection auf events in kube-system: Kubernetes-Events gelöscht (niedrig). Die Events hätten das Scheduling der DaemonSet-Pods und das Ziehen des Miner-Images gezeigt. Das außerhalb des Clusters gespeicherte Audit-Log hat weiterhin alles. (Auf EKS, dessen verwaltete Policy Events gar nicht protokolliert, wäre dieser Schritt nicht sichtbar.)
Das False Positive im Bericht
Ein Finding hat mit dem Angriff nichts zu tun: Interaktiver Befehl in einem Container (exec / attach) (mittel) für alice@example.com um 10:12:03, ein sh in einen API-Pod in shop. Es ist eine legitime Debugging-Session von ihrer üblichen IP mit ihrem üblichen kubectl. Genau so sollten exec-Findings behandelt werden: mit der verantwortlichen Person klären, vermerken, weitermachen. Siehe kubectl exec erkennen.
Was die Responder als Nächstes tun
Gemäß der Checkliste des Analyzers und dem Incident-Response-Leitfaden:
- Sichern: Audit-Logs exportieren, die Specs von DaemonSet, CronJob, ServiceAccount und ClusterRoleBinding sichern, Snapshots von worker-1 (die Shell) und den anderen Nodes erstellen (privilegierte Pods liefen auf allen drei).
- Eindämmen:
node-health-adminlöschen, beide Service Accounts löschen und neu anlegen (das einjährige Token stirbt mitnode-health), den Zugriff auf den API-Server beschränken, das Dashboard vom Netz nehmen. - Bereinigen: CronJob samt Jobs und Pods sowie das DaemonSet löschen; in allen Namespaces nach Image und Pool suchen.
- Wiederherstellen: alle drei Nodes ersetzen; jedes Secret rotieren, beginnend mit den Cloud-Zugangsdaten, dem GitHub-App-Schlüssel und dem CI-Token, die über den Cluster hinausreichen.
- Über den Cluster hinaus eingrenzen: Die gestohlenen Cloud-Zugangsdaten und das CI-Token müssen in den Audit-Logs von Cloud und CI verfolgt werden.
- Härten: kein exponiertes Dashboard, Dashboard-Rolle nach Least Privilege, Pod Security Admission auf
kube-system, nur vertrauenswürdige Registries.
Selbst ausprobieren
Öffnen Sie den Analyzer, klicken Sie auf Beispiel ausprobieren und lesen Sie mit: Timeline, Entitäten-Pivot auf 203.0.113.45 und das Detailblatt jedes Events zeigen alles, was hier beschrieben ist. Die Schritt-für-Schritt-Anleitung erklärt jedes Panel.