
Proof-of-Concept, das einen Container-Ausbruch auf Kubernetes über die Kernel-Page-Cache-Korruption von CVE-2026-31431 demonstriert und eine Codeausführung auf Knotenebene aus einem unprivilegierten Container erreicht.
Ein Proof-of-Concept, der demonstriert, wie ein vollständig unprivilegierter Container durch Ausnutzung des Linux-Kernel-Bugs CVE-2026-31431 (Page-Cache-Korruption) über gemeinsame Container-Image-Layer Codeausführung auf Knotenebene in Kubernetes erreichen kann.
Haftungsausschluss: Dieses Repository dient ausschließlich Bildungs- und Verteidigungszwecken. Verwenden Sie es ausschließlich auf Systemen, die Ihnen gehören oder für die Sie eine ausdrückliche Testgenehmigung besitzen.
CVE-2026-31431 („Copy Fail") ist eine Schwachstelle im Linux-Kernel im Copy-on-Write-Pfad (CoW) des Page-Cache. Eine AF_ALG-Splice-Race-Bedingung ermöglicht es einem unprivilegierten Prozess, die Page-Cache-Seiten einer schreibgeschützten Datei zu korrumpieren. Die Korruption bleibt im Kernel-Page-Cache bestehen und ist für jeden Prozess sichtbar, der die Datei anschließend liest oder ausführt – einschließlich Prozessen in anderen Containern oder auf dem Host.
Vollständige Details zur ursprünglichen Schwachstelle finden Sie unter copy.fail.
Die Angriffskette besteht aus drei Phasen: Page-Cache-Korruption, containerübergreifende Ausbreitung und privilegierte Ausführung.
Der AF_ALG-Subsystem (Krypto) des Kernels stellt eine socketbasierte Schnittstelle für kryptografische Operationen im Userspace bereit. Der Exploit nutzt eine Race-Bedingung in der Art und Weise, wie der Kernel splice() von einer Datei in einen AF_ALG-Socket behandelt:
/usr/sbin/ipset) schreibgeschützt.authesn(hmac(sha256),cbc(aes)) gebunden ist.MSG_MORE, um dem Kernel mitzuteilen, dass weitere Daten erwartet werden.splice() Sie den Inhalt der Zieldatei von einem fd → Pipe → AF_ALG-Socket.Der Exploit wiederholt dies für jedes 4-Byte-Fenster, bis die gesamten gecachten Seiten der Zieldatei mit einer benutzerdefinierten Payload überschrieben sind.
Für die Datei ist keine Schreibberechtigung erforderlich. Die Datei auf der Festplatte bleibt unverändert – nur der In-Memory-Page-Cache wird korrumpiert.
Container-Runtimes (containerd, CRI-O) verwenden Overlay-Dateisysteme. Wenn zwei Container denselben Image-Layer gemeinsam nutzen, bedient der Kernel ihre Dateilesevorgänge über dieselben Page-Cache-Seiten.
Dieses PoC-Image ist FROM registry.k8s.io/kube-proxy:v1.35.2 erstellt. Das kube-proxy-DaemonSet auf jedem Kubernetes-Knoten verwendet exakt denselben Basis-Layer. Dadurch wird /usr/sbin/ipset in beiden Containern auf dieselbe Gruppe von Page-Cache-Seiten abgebildet.
Wenn der unprivilegierte PoC-Container den Page-Cache von ipset korrumpiert, ist die Korruption sofort für den privilegierten kube-proxy-Container auf demselben Knoten sichtbar – ohne jegliche containerübergreifende Kommunikation.
kube-proxy läuft als privilegiertes DaemonSet mit hostNetwork: true. Es ruft regelmäßig /usr/sbin/ipset auf, um iptables/ipset-Regeln zu verwalten. Wenn es ipset als Nächstes ausführt, lädt der Kernel die korrumpierten Page-Cache-Seiten und führt die Payload des Angreifers mit den vollständigen Privilegien von kube-proxy aus:
Die Payload in diesem PoC (payload/payload.c) mountet einfach das Host-Root-Dateisystem und schreibt eine Markierungsdatei nach /root/res als Nachweis der Codeausführung auf Knotenebene.
┌──────────────────────────┐ ┌──────────────────────────┐
│ PoC-Container │ │ kube-proxy-Container │
│ (unprivilegiert) │ │ (privilegiert) │
│ │ │ │
│ 1. Öffnet /usr/sbin/ipset│ │ │
│ (schreibgeschützt) │ │ │
│ │ │ │
│ 2. AF_ALG-Splice-Race │ │ │
│ korrumpiert Page- │ │ │
│ Cache │ │ │
│ │ │ │ │
└──────────┼───────────────┘ └──────────────────────────┘
│ │
▼ │
┌─────────────────────┐ │
│ Kernel-Page-Cache │ │
│ /usr/sbin/ipset │◄────────────────────┘
│ (KORRUMPIERT) │ 3. kube-proxy führt ipset aus
│ enthält Payload- │ → lädt korrumpierte Seiten
│ Bytes des Angreifers│ → Payload läuft als Root
└─────────────────────┘ auf dem Host
.
├── cmd/copyfail/main.go # Einstiegspunkt; bettet kompilierte Payload ein
├── internal/
│ ├── exploit/
│ │ ├── exploit.go # Kern-Exploit: AF_ALG-Splice-Race-Schleife
│ │ └── patch.go # Teilt Payload in 4-Byte-Patch-Fenster auf
│ └── alg/
│ └── alg.go # AF_ALG-AEAD-Socket-Abstraktion
├── payload/
│ ├── payload.c # Validierungspayload (Host-FS mounten, Marker schreiben)
│ └── nolibc/ # Winzige libc des Kernels für statische, abhängigkeitsfreie Payloads
├── deploy/
│ └── poc.yaml # Kubernetes-Deployment-Manifest
├── Dockerfile # Erstellt FROM kube-proxy zur gemeinsamen Nutzung von Image-Layern
├── Makefile # Build-Orchestrierung
└── docs/ # Validierungsnachweise von ACK (Alibaba Cloud)
x86_64-linux-gnu-gcc)imagePullPolicy: IfNotPresent (Standard) ausführt# Payload + Go-Binary erstellen
make build
# Docker-Image erstellen
make docker-build
# Erstellen und zu GHCR pushen
make docker-push IMAGE=ghcr.io/<you>/copy-fail-poc TAG=latest
Für arm64-Ziele:
make build CC=aarch64-linux-gnu-gcc GOARCH=arm64
kubectl apply -f deploy/poc.yaml
Das Deployment erstellt einen einzelnen unprivilegierten Pod. Er:
/bin/copyfail -target /usr/sbin/ipset aus, um den Page-Cache zu korrumpieren.Nachdem kube-proxy ipset als Nächstes ausgeführt hat (dies geschieht normalerweise innerhalb von Sekunden aufgrund seiner Reconcile-Schleife oder beim nächsten Neustart), überprüfen Sie den Knoten:
# Per SSH auf den Knoten einloggen oder einen privilegierten Debug-Pod verwenden
cat /root/res
# Erwartete Ausgabe: [*] success
Das Vorhandensein von /root/res auf dem Host-Dateisystem beweist, dass vom Angreifer bereitgestellter Code mit Privilegien auf Knotenebene ausgeführt wurde – geschrieben aus dem Kontext des privilegierten kube-proxy-Containers heraus.
kubectl delete -f deploy/poc.yaml
# Auf den betroffenen Knoten den Marker entfernen und kube-proxy neu starten:
rm -f /root/res
systemctl restart kubelet # oder den kube-proxy-Pod löschen, um ein erneutes Ziehen zu erzwingen
kube-proxy ist ein ideales Ziel, weil es:
privileged: true, hostNetwork: true.imagePullPolicy: IfNotPresent verwendet – sobald das Image des Angreifers gezogen wurde und denselben Basis-Layer teilt, werden die Seiten des Overlay-Unterverzeichnisses gemeinsam genutzt.Jedes privilegierte DaemonSet, dessen Image eine vorhersehbare Binary enthält, könnte auf dieselbe Weise angegriffen werden.
Die Standard-Payload (payload/payload.c) ist ein reines Validierungsprogramm, das eine Markierungsdatei schreibt. So erstellen Sie eine benutzerdefinierte Payload:
payload/payload.c. Das Programm wird gegen nolibc (die minimale C-Bibliothek des Kernels) für eine statische, abhängigkeitsfreie Binary erstellt.make payload aus, um zu cross-kompilieren.//go:embed in die Go-Binary eingebettet.tools/include/nolibc/)Der Go-Exploit-Code in diesem Repository wird wie besehen für Forschungszwecke bereitgestellt.
Die Payload (payload/payload.c) ist von copy-fail-c abgeleitet und unter LGPL-2.1-or-later ODER MIT dual-lizenziert. Siehe LICENSE-LGPL und LICENSE-MIT.