kubectl exec, attach und port-forward in Audit-Logs finden
Wie kubectl exec, attach, cp, port-forward und nodes/proxy in Kubernetes-Audit-Logs erscheinen, was der Befehl verrät und wie Sie Missbrauch erkennen.
TL;DR. kubectl exec und kubectl attach sind Requests auf die Subressourcen pods/exec und pods/attach; kubectl port-forward trifft pods/portforward. Filtern Sie auf objectRef.subresource, akzeptieren Sie sowohl create als auch get als Verb, erwarten Sie den Response-Code 101 und lesen Sie den Befehl aus den command=-Parametern in requestURI. Ein exec durch einen Service Account ist selten und sollte als hoher Schweregrad behandelt werden. Zugriff auf nodes/proxy erreicht das Kubelet direkt und ist schlimmer. Was innerhalb der Session getippt wird, wird nicht protokolliert.
Einen Befehl in einem Container auszuführen ist die ATT&CK-Technik T1609, Container Administration Command. Es ist auch das, was legitime Administratoren bei einem Ausfall am häufigsten tun. Ziel ist nicht, jedes exec zu markieren, sondern jedes exec erklärbar zu machen.
So sieht exec im Audit-Log aus
kubectl exec -it api-6c9f7d8b5-q2w8x -n shop -- sh erzeugt ein Event wie dieses (gekürzt, fiktive Namen):
{
"verb": "create",
"stage": "ResponseStarted",
"requestURI": "/api/v1/namespaces/shop/pods/api-6c9f7d8b5-q2w8x/exec?command=sh&container=api&stdin=true&stdout=true&tty=true",
"user": { "username": "alice@example.com" },
"objectRef": { "resource": "pods", "namespace": "shop", "name": "api-6c9f7d8b5-q2w8x", "subresource": "exec" },
"responseStatus": { "code": 101 }
}
Wissenswert:
- Verb. Der exec-Endpunkt wird traditionell als
createautorisiert und auditiert. Seit Kubernetes 1.31 streamt kubectl standardmäßig über WebSockets, und ein WebSocket-Upgrade beginnt als HTTP-GET; je nach Version sehen Sie alsoget. Suchen Sie nach beidem. - Stage. exec ist ein lang laufender Request. Das
ResponseStarted-Event wird beim Öffnen der Session geschrieben,ResponseCompleteerst beim Schließen. Lässt die Policy nichts weg, bekommen Sie beide. - Response-Code.
101 Switching Protocolsheißt, die Session wurde aufgebaut. Ein403heißt, jemand hat es versucht und RBAC hat abgelehnt, was für sich schon interessant ist. - Befehl. Jedes Argument ist ein eigener, URL-kodierter
command=-Parameter:command=chroot&command=%2Fhost&command=shergibtchroot /host sh. Da er in der URI steht, wird er auch auf LevelMetadataprotokolliert.
Die übrigen Felder erklärt die Anatomie eines Audit-Events.
attach, cp, debug und port-forward
| Client-Aktion | Audit-Spur | Worauf achten |
|---|---|---|
kubectl attach | pods/attach | Kein Befehl: hängt sich an den Hauptprozess des Containers |
kubectl cp | pods/exec mit command=tar | Richtung (tar cf = aus dem Pod kopieren, tar -x… = in den Pod kopieren) und Pfade |
kubectl debug (Ephemeral Container) | patch auf pods/ephemeralcontainers, dann pods/attach | Das Image und ob es den Prozess-Namespace des Ziels teilt |
kubectl port-forward | pods/portforward mit ports= | Welcher Port: 5432, 6379, 3306 sind Datenbanken |
kubectl proxy / API-Proxy zu einem Node | nodes/proxy | Direkter Zugriff auf die Kubelet-API |
kubectl cp verdient einen eigenen Blick: Eine Datei in einen Pod zu kopieren ist ein Weg, Werkzeuge abzulegen, ohne ein neues Image zu ziehen, und aus einem Pod zu kopieren ist Exfiltration über den API-Server.
kubectl port-forward öffnet einen Tunnel vom Rechner des Clients ins Pod-Netz. Er umgeht Ingress-Controller und, für den Traffic des Clients, Network Policies, die nur Pod-zu-Pod-Traffic filtern. Ein port-forward auf einen Datenbank-Pod durch eine Identität, die das sonst nie tut, ist eine starke Spur.
nodes/proxy: exec ohne exec-Event
Die Subressource nodes/proxy erlaubt es, über den API-Server mit der Kubelet-API zu sprechen. Das Kubelet kann Befehle in jedem Container dieses Nodes ausführen und dessen Logs lesen. Der API-Server auditiert den nodes/proxy-Request, aber nicht als pods/exec, sodass exec-basierte Erkennungen ihn übersehen. Die RBAC Good Practices von Kubernetes führen den Zugriff auf die Proxy-Subressource von Nodes als Weg zur Rechteausweitung.
Schlimmer noch: Wer den Kubelet-Port eines Nodes mit gültigen Zugangsdaten erreicht, spricht direkt mit ihm; diese Aufrufe erreichen nie den API-Server und erscheinen nie im Audit-Log. Hier sind die Authentifizierungs- und Autorisierungseinstellungen des Kubelets die Kontrolle.
Admin-Arbeit von Missbrauch unterscheiden
Der Analyzer auf der Startseite teilt exec in vier Regeln auf:
| Regel | Schweregrad | Logik |
|---|---|---|
| Interaktiver Befehl in einem Container (exec / attach) | Mittel | pods/exec oder pods/attach erlaubt, durch eine menschliche Nicht-System-Identität |
| Service Account hat Befehl in einem Container ausgeführt | Hoch | Dasselbe, durch eine system:serviceaccount:-Identität |
| Port-Forward zu einem Pod | Mittel | pods/portforward erlaubt, Nicht-System-Identität |
| Kubelet-API über nodes/proxy erreicht | Hoch | nodes/proxy erlaubt, Nicht-System-Identität |
Warum diese Aufteilung? Workloads führen fast nie exec in andere Pods aus. Tut ein Service Account das, ist es meist ein aus einem Pod entnommenes Token, das eine Person wiederverwendet, oft mit kubectl/ als User Agent von einer unerwarteten IP, was zwei weitere Regeln erkennen (siehe Diebstahl von Service-Account-Tokens). CI-Systeme und manche Operatoren nutzen exec legitim; setzen Sie sie nach Prüfung explizit auf die Allow-List.
Bei menschlichen Identitäten triagieren Sie jede Session mit vier Fragen:
- Wer und woher? Bekannter Admin, bekannter IP-Bereich, üblicher User Agent?
- Wann? In einem Change-Fenster oder Incident, oder zu einer ungewöhnlichen Uhrzeit?
- Wo? Welcher Namespace und Pod; ist es ein privilegierter Pod oder eine Komponente in
kube-system? - Was? Der Befehl: interaktives
sh/bashoder etwas wiecat /var/run/secrets/kubernetes.io/serviceaccount/token,curl … | sh,chroot /host,nsenter?
Ein chroot /host oder nsenter --target 1 in einem Pod mit hostPath auf / oder hostPID ist ein Container-Escape; siehe privilegierte Pods und Container-Escape.
Hunting-Abfragen
Rohe JSON-Zeilen mit jq:
jq -c 'select(.objectRef.subresource == "exec" or .objectRef.subresource == "attach"
or .objectRef.subresource == "portforward")
| {t: .requestReceivedTimestamp, user: .user.username, ip: .sourceIPs,
ns: .objectRef.namespace, pod: .objectRef.name,
sub: .objectRef.subresource, uri: .requestURI, code: .responseStatus.code}' audit.log
EKS, CloudWatch Logs Insights:
fields @timestamp, user.username, sourceIPs.0, objectRef.namespace, objectRef.name, requestURI
| filter @logStream like "kube-apiserver-audit"
| filter objectRef.subresource in ["exec", "attach", "portforward"]
| sort @timestamp desc
AKS, Log Analytics (ressourcenspezifische Tabelle):
AKSAudit
| where RequestUri has "/exec" or RequestUri has "/attach" or RequestUri has "/portforward"
| project TimeGenerated, User, SourceIps, UserAgent, RequestUri, ResponseStatus
Auf GKE filtern Sie Cloud Logging auf protoPayload.methodName:"pods.exec" (sowie pods.attach, pods.portforward).
Angriffsfläche verkleinern
- Beschränken Sie
pods/exec,pods/attachundpods/portforwardauf eine kleine Break-Glass-Gruppe; es sind eigene RBAC-Ressourcen, Sie können alsogetauf Pods ohne sie vergeben. - Vergeben Sie
nodes/proxyweder an Personen noch an Workloads. - Alarmieren Sie bei jedem exec durch einen Service Account und bei jedem exec in
kube-system. - Bewahren Sie das Audit-Log auf: exec-Sessions sind kurz, und der Pod ist morgen vielleicht weg.
FAQ
Wie erkenne ich kubectl exec in Kubernetes-Audit-Logs?
Suchen Sie Events, deren objectRef.subresource auf der Ressource pods exec (oder attach) ist. Je nach Client- und Serverversion ist das Verb create oder get, und der Response-Code ist 101, wenn das Streaming-Upgrade gelingt. Der Befehl steht in requestURI als wiederholte command=-Parameter.
Kann ich sehen, was in einer kubectl-exec-Shell getippt wurde?
Nein. Das Audit-Log zeichnet den Request auf, der die Session geöffnet hat, und dessen initialen Befehl, nicht die danach ausgetauschten Bytes. Tastatureingaben und Prozesse im Container erfordern Runtime-Werkzeuge wie Falco oder ein EDR auf dem Node.
Taucht kubectl cp in Audit-Logs auf?
Ja, als exec. kubectl cp führt über pods/exec tar im Container aus, das Audit-Event zeigt also command=tar mit seinen Argumenten in requestURI.