
PoC: полностью непривилегированный побег из контейнера до выполнения кода на уровне узла в Kubernetes через повреждение page-cache CVE-2026-31431 + общие слои образов. Проверено на Alibaba Cloud ACK, Amazon EKS и Google GKE.
Proof-of-concept, демонстрирующий, как полностью непривилегированный контейнер может добиться выполнения кода на уровне узла в Kubernetes, эксплуатируя ошибку повреждения page-cache ядра Linux CVE-2026-31431 через общие слои образов контейнеров.
Основной примитив атаки: любой привилегированный DaemonSet, разделяющий слои образа с контейнером, контролируемым атакующим, может быть использован для побега из контейнера. В этом PoC в качестве конкретного примера используется kube-proxy, но метод применим к любой привилегированной рабочей нагрузке в кластере.
Проверено на Alibaba Cloud ACK, Amazon EKS и Google GKE — непривилегированный pod записывает [*] success в файловую систему узла через привилегированный DaemonSet kube-proxy:
| Alibaba Cloud ACK (kernel 6.6.88) | Amazon EKS (kernel 6.12.79) | Google GKE (kernel 6.12.68) |
|---|---|---|
![]() | ![]() | ![]() |
Отказ от ответственности: Этот репозиторий опубликован только в образовательных и защитных целях. Используйте его исключительно в системах, которыми вы владеете или на тестирование которых имеете явное разрешение.
CVE-2026-31431 («Copy Fail») — уязвимость ядра Linux в пути Copy-on-Write (CoW) page-cache. Гонка splice через AF_ALG позволяет непривилегированному процессу повредить страницы page-cache файла, открытого только для чтения. Повреждение сохраняется в page-cache ядра и видно каждому процессу, который впоследствии читает или исполняет этот файл, — включая процессы в других контейнерах или на узле.
Полные сведения об исходной уязвимости см. на copy.fail.
Атака использует три свойства, которые часто сосуществуют в кластерах Kubernetes:
privileged: true, hostNetwork: true, широкими capabilities и т.д.), которые периодически выполняют бинарные файлы из своего образа.Когда эти условия совпадают, непривилегированный pod может повредить бинарный файл в общем слое образа, а привилегированный DaemonSet на том же узле неосознанно выполнит повреждённый бинарный файл со своими повышенными привилегиями — достигая полного выполнения кода на уровне узла.
Цель уязвимости НЕ ограничивается kube-proxy. Любой привилегированный DaemonSet (агенты мониторинга, CNI-плагины, сборщики логов, агенты безопасности и т.д.), чей образ контейнера разделяет слои с образом, контролируемым атакующим, является подходящей целью.
Цепочка атаки состоит из трёх этапов: повреждение page-cache, распространение между контейнерами и привилегированное выполнение.
Подсистема ядра AF_ALG (crypto) предоставляет интерфейс на основе сокетов для криптографических операций из пользовательского пространства. Эксплойт злоупотребляет состоянием гонки в том, как ядро обрабатывает splice() из файла в сокет AF_ALG:
authesncs(hmac(sha256),cbc(aes)).MSG_MORE, сообщая ядру, что ожидаются ещё данные.splice() передайте содержимое целевого файла из fd → pipe → сокет AF_ALG.Эксплойт повторяет это для каждого 4-байтового окна, пока все кэшированные страницы целевого бинарного файла не будут перезаписаны специальной полезной нагрузкой.
Права на запись в файл не требуются. Файл на диске остаётся неизменным — повреждён только page-cache в памяти.
Контейнерные рантаймы используют overlay-файловые системы. Когда два контейнера используют один и тот же слой образа, ядро обслуживает чтение файлов из одних и тех же страниц page-cache.
Атакующий собирает образ PoC FROM того же базового образа, что и целевой привилегированный DaemonSet. Поскольку оба контейнера используют один и тот же lower-dir в overlay, бинарные файлы в общем слое сопоставляются с идентичными страницами page-cache.
Когда непривилегированный PoC-контейнер повреждает page-cache бинарного файла, повреждение немедленно становится видимым привилегированному контейнеру на том же узле — без какой-либо связи между контейнерами.
Когда привилегированный DaemonSet в следующий раз выполнит любой повреждённый бинарный файл (в рамках своего обычного цикла работы), ядро загрузит повреждённые страницы page-cache. Полезная нагрузка атакующего выполняется с полными привилегиями DaemonSet, потенциально включая:
Полезная нагрузка в этом PoC (payload/payload.c) просто монтирует корневую файловую систему узла и записывает файл-маркер в /root/res как доказательство выполнения кода на уровне узла.
┌──────────────────────────┐ ┌──────────────────────────┐
│ PoC Container │ │ Privileged DaemonSet │
│ (unprivileged) │ │ (e.g. kube-proxy, │
│ │ │ monitoring agent, etc.)│
│ 1. Open target binary │ │ │
│ (read-only) │ │ │
│ │ │ │
│ 2. AF_ALG splice race │ │ │
│ corrupts page cache │ │ │
│ │ │ │ │
└──────────┼───────────────┘ └──────────────────────────┘
│ │
▼ │
┌─────────────────────┐ │
│ Kernel Page Cache │ │
│ │◄────────────────────┘
│ Shared-layer binary │ 3. DaemonSet executes the
│ (CORRUPTED) │ corrupted binary
│ contains attacker's │ → loads corrupted pages
│ payload bytes │ → payload runs with
└─────────────────────┘ DaemonSet's privileges
PoC успешно проверен на следующих управляемых платформах Kubernetes:



Во всех трёх случаях непривилегированный PoC-pod успешно записал файл-маркер [*] success в файловую систему узла, что доказывает выполнение кода на уровне узла через привилегированный DaemonSet kube-proxy.
Полные руководства (анализ слоёв образа, шаги сборки, развёртывание):
В этом PoC в качестве цели используется kube-proxy, поскольку это один из самых распространённых привилегированных DaemonSet в кластерах Kubernetes. Предоставлены три варианта:
FROM registry.k8s.io/kube-proxy:v1.35.2 (см. Dockerfile)FROM public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023 (см. Dockerfile.eks)FROM us-central1-artifactregistry.gcr.io/gke-release/gke-release/kube-proxy:v1.35.3-gke.1234000 (см. Dockerfile.gke)Все варианты повреждают такие бинарные файлы, как /usr/sbin/ipset, /usr/sbin/nft, /usr/sbin/xtables-legacy-multi и /usr/sbin/xtables-nft-multi.
Важные оговорки:
ipset только при настройке в режиме ipvs. Режим по умолчанию (iptables) не использует ipset. План по выводу ipvs из эксплуатации см. в kubernetes/enhancements#5495.ipset, nft, xtables-legacy-multi, xtables-nft-multi), чтобы охватить различные режимы прокси, но их вызов зависит от конфигурации кластера.Если в вашем кластере kube-proxy не привилегирован, принцип атаки всё равно работает — вам просто нужно найти другой привилегированный DaemonSet, который разделяет слои образа с базовым образом, из которого вы можете собрать свой.
Чтобы адаптировать этот PoC для другого привилегированного DaemonSet:
FROM того же базового образа, который использует этот DaemonSet..
├── cmd/copyfail/main.go # Entry point; embeds compiled payload
├── internal/
│ ├── exploit/
│ │ ├── exploit.go # Core exploit: AF_ALG splice race loop
│ │ └── patch.go # Splits payload into 4-byte patch windows
│ └── alg/
│ └── alg.go # AF_ALG AEAD socket abstraction
├── payload/
│ ├── payload.c # ACK/upstream payload (mount /dev/vda3 ext4)
│ ├── payload-eks.c # EKS payload (NVMe/Xen device auto-detection)
│ ├── payload-gke.c # GKE payload (COS/Ubuntu device auto-detection)
│ └── nolibc/ # Kernel's tiny libc for static, no-dependency payloads
├── deploy/
│ ├── poc.yaml # Kubernetes Deployment manifest (ACK/upstream)
│ ├── poc-eks.yaml # EKS Deployment manifest
│ └── poc-gke.yaml # GKE Deployment manifest
├── Dockerfile # ACK/upstream: FROM registry.k8s.io/kube-proxy
├── Dockerfile.eks # EKS: FROM eks-distro-minimal-base-iptables
├── Dockerfile.gke # GKE: FROM gke-release/kube-proxy
├── Makefile # Build orchestration (includes *-eks and *-gke targets)
└── docs/
├── eks-poc.md # EKS PoC full walkthrough
├── gke-poc.md # GKE PoC full walkthrough
├── ack-poc-res.png # ACK validation screenshot
├── eks-poc-res.png # EKS validation screenshot
└── gke-poc-res.png # GKE validation screenshot
x86_64-linux-gnu-gcc)imagePullPolicy: IfNotPresent у целевого DaemonSet (значение по умолчанию в Kubernetes)# Build payload + Go binary
make build
# Build Docker image
make docker-build
# Build and push to GHCR
make docker-push IMAGE=ghcr.io/<you>/copy-fail-poc TAG=latest
# Build EKS payload + Go binary + Docker image
make docker-build-eks
# Build and push to GHCR
make docker-push-eks IMAGE=ghcr.io/<you>/copy-fail-poc
Для целей arm64 (Graviton):
make build-eks CC=aarch64-linux-gnu-gcc GOARCH=arm64
# Build GKE payload + Go binary + Docker image
make docker-build-gke
# Build and push to GHCR
make docker-push-gke IMAGE=ghcr.io/<you>/copy-fail-poc
Для узлов arm64:
make docker-build-gke CC=aarch64-linux-gnu-gcc GOARCH=arm64 PLATFORM=linux/arm64
# ACK / upstream Kubernetes
kubectl apply -f deploy/poc.yaml
# Amazon EKS
kubectl apply -f deploy/poc-eks.yaml
# Google GKE
kubectl apply -f deploy/poc-gke.yaml
Deployment создаёт один непривилегированный pod. Он:
/bin/copyfail, чтобы повредить page-cache целевых бинарных файлов в общем слое образа.После того как целевой привилегированный DaemonSet в следующий раз выполнит повреждённый бинарный файл (для kube-proxy это обычно происходит в течение секунд благодаря циклу согласования), проверьте узел:
# SSH into the node, or use a privileged debug pod
# ACK / EKS (writable root filesystem)
cat /root/res
# Expected output: [*] success
# GKE COS nodes (read-only root, writable stateful partition)
cat /mnt/stateful_partition/copyfail-res
# Expected output: [*] success
Наличие файла-маркера в файловой системе узла доказывает, что код, предоставленный атакующим, выполнился с привилегиями уровня узла — из контекста контейнера привилегированного DaemonSet.
kubectl delete -f deploy/poc.yaml # or poc-eks.yaml / poc-gke.yaml
# On the affected node(s), remove the marker and restart the target DaemonSet:
rm -f /root/res # ACK / EKS
rm -f /copyfail-res /mnt/stateful_partition/copyfail-res # GKE COS nodes
# For kube-proxy: delete the pod to force image layer re-read
kubectl delete pod -n kube-system -l k8s-app=kube-proxy --field-selector spec.nodeName=<node>
Полезная нагрузка по умолчанию (payload/payload.c) — это программа только для проверки, которая записывает файл-маркер. Чтобы собрать собственную полезную нагрузку:
payload/payload.c. Программа собирается с nolibc (минимальной C-библиотекой ядра) для статического бинарного файла без зависимостей.make payload для кросс-компиляции.//go:embed.privileged: true.AF_ALG. Правило доступно через механизмы AppArmor и BPF.tools/include/nolibc/)Go-код эксплойта в этом репозитории предоставляется как есть, в исследовательских целях.
Полезная нагрузка (payload/payload.c) является производной от copy-fail-c и распространяется по двойной лицензии LGPL-2.1-or-later OR MIT. См. LICENSE-LGPL и LICENSE-MIT.
| Свойство | Значение |
|---|
| Платформа | Alibaba Cloud Container Service for Kubernetes (ACK) |
| Kubernetes | v1.35.2 |
| Ядро узла | 6.6.88-4.2.alnx4.x86_64 |
| kube-proxy | registry-cn-*.ack.aliyuncs.com/acs/kube-proxy:v1.35.2-aliyun.1 |
| Базовый образ | registry.k8s.io/kube-proxy:v1.35.2 (апстрим) |
| Корневое устройство | /dev/vda3 (ext4) |
| Свойство | Значение |
|---|
| Платформа | Amazon Elastic Kubernetes Service (EKS) |
| Kubernetes | v1.35.4 |
| Ядро узла | 6.12.79-101.147.amzn2023.x86_64 |
| kube-proxy | ***.dkr.ecr.***.amazonaws.com.cn/eks/kube-proxy:v1.35.3-eksbuild.2 |
| Базовый образ | public.ecr.aws/eks-distro-build-tooling/eks-distro-minimal-base-iptables:2026-03-11-1773190710.2023 |
| Корневое устройство | /dev/nvme0n1p1 (xfs) |
| Свойство | Значение |
|---|
| Платформа | Google Kubernetes Engine (GKE) |
| Kubernetes | v1.35.3-gke.1234000 |
| ОС узла | Container-Optimized OS (COS) 125, BUILD_ID 19216.220.72 |
| Ядро узла | 6.12.68+ x86_64 |
| kube-proxy | us-central1-artifactregistry.gcr.io/gke-release/gke-release/kube-proxy:v1.35.3-gke.1234000 |
| Базовый образ | Тот же, что у kube-proxy (образ Artifact Registry, управляемый провайдером GKE) |
| Корневое устройство | /dev/dm-0 (ext2, только чтение); /dev/sda1 (ext4, монтируемый stateful-раздел) |
| Путь маркера | /mnt/stateful_partition/copyfail-res |