На каждом втором red team-проекте с контейнерной инфраструктурой я вижу одну и ту же картину: команда разработки развернула Kubernetes, настроила CI/CD, накатила Helm-чарты - и считает, что контейнеры сами по себе всё изолируют. Мол, контейнер же! Почти виртуалка! Нет. Контейнер - это группа процессов, отгороженных от хоста набором Linux-примитивов: namespaces, cgroups и capabilities. Никакого отдельного ядра. Стоит одному из этих заборчиков дать трещину - атакующий вываливается на хост, а оттуда до cluster-admin остаётся несколько команд.
Дальше - полная цепочка атаки при пентесте контейнеров Docker и Kubernetes: от разведки и container breakout до эскалации привилегий в k8s и захвата кластера. Каждый шаг - из реальной практики, с командами, CVE и ссылками на MITRE ATT&CK.
Разведка контейнерной инфраструктуры снаружи и изнутри
Любой пентест контейнеров начинается с определения поверхности атаки. Контейнерная инфраструктура выставляет наружу характерный набор портов и сервисов - опытный глаз распознаёт их мгновенно.Внешнее обнаружение Docker и Kubernetes
При внешнем сканировании ищем конкретные порты: 2375/tcp (Docker API без TLS), 2376/tcp (Docker API с TLS), 5000/tcp (Docker Registry), 6443/tcp (Kubernetes API Server), 8080/tcp (K8s API без аутентификации - на self-hosted кластерах бывает), 10250/tcp (kubelet API), 10255/tcp (kubelet read-only), 2379/tcp (etcd). Проверка тривиальна:curl -s http://<ip>:2375/version для Docker или curl -k https://<ip>:6443/api/v1 для Kubernetes API. Если в ответ прилетает JSON с версиями - вы нашли точку входа.В Shodan и Censys контейнерные сервисы обнаруживаются запросами вида
product:"Kubernetes" port:6443, docker port:2375 200 OK, http.title:"Kubernetes Dashboard". На реальных проектах я неоднократно находил открытые kubelet API и незащищённые etcd-инстансы именно так - просто вбивая запрос в Shodan.С точки зрения MITRE ATT&CK это этап Discovery - техника Container and Resource Discovery (T1613). Определяем топологию контейнерной среды перед тем, как планировать дальнейшие шаги.
Ориентирование внутри скомпрометированного контейнера
Если начальная точка - скомпрометированный контейнер (через уязвимое веб-приложение, RCE, утёкшие credentials), первым делом определяем контекст. Нужно понять: мы в Docker-контейнере или в Kubernetes-поде, какие привилегии и capabilities доступны, какие volumes смонтированы.Проверка идёт по простой цепочке. Файлы в
/var/run/secrets/kubernetes.io/serviceaccount/ - значит мы внутри k8s-пода: там лежат token, ca.crt и namespace. Команда cat /proc/1/cgroup покажет идентификаторы cgroup - строки с docker или kubepods подтверждают контейнеризацию. Через capsh --print (или чтение /proc/1/status с фильтром по CapEff) смотрим текущие capabilities. Наличие CAP_SYS_ADMIN в эффективном наборе - считайте, container breakout у вас в кармане.
Bash:
# Быстрая ориентировка внутри контейнера
cat /proc/1/cgroup | grep -E "docker|kubepods|containerd"
ls -la /var/run/secrets/kubernetes.io/serviceaccount/
mount | grep -E "docker.sock|hostPath"
cat /proc/1/status | grep -i cap
env | grep -i kube
ls -la /var/run/docker.sock. Его монтирование внутрь контейнера - это, по сути, передача root-доступа к хосту. Ещё стоит посмотреть mount - hostPath-монтирования часто открывают прямой путь к файловой системе хоста.Container breakout - техники побега из Docker
Docker escape - ключевой этап в цепочке атаки. По классификации MITRE ATT&CK это Escape to Host (T1611, Privilege Escalation). Условия для побега делятся на три категории: уязвимости runtime, привилегированные контейнеры и мисконфигурации.Эксплуатация Docker socket
Смонтированный/var/run/docker.sock - самый частый вектор побега из контейнера Docker на реальных пентестах. Этот unix-сокет даёт прямой доступ к Docker daemon API на хосте. Я встречаю его в CI/CD-агентах (Jenkins, GitLab Runner), мониторинг-контейнерах и «admin-образах», куда его монтируют «для удобства». Удобство - оно такое, да.Если внутри контейнера есть клиент
docker, атака тривиальна: docker -H unix:///var/run/docker.sock run --rm -it --privileged --pid=host -v /:/host alpine chroot /host /bin/sh - новый привилегированный контейнер с полным доступом к ФС хоста. Но даже без docker-клиента хватит curl:
Bash:
# Создание привилегированного контейнера через Docker API
CONTAINER=$(curl -s -X POST --unix-socket /var/run/docker.sock \
-H "Content-Type: application/json" \
-d '{"Image":"alpine","Cmd":["sh"],"HostConfig":{"Privileged":true,"Binds":["/:/host"]}}' \
http://localhost/containers/create | grep -o '"Id":"[^"]*"' | cut -d'"' -f4)
curl -s -X POST --unix-socket /var/run/docker.sock \
http://localhost/containers/${CONTAINER}/start
/host. По MITRE ATT&CK это комбинация Container Administration Command (T1609) и Deploy Container (T1610).Нюанс из практики: иногда между контейнером и Docker socket стоит прокси-фильтр (например, docker-socket-proxy от Tecnativa), который блокирует опасные вызовы API. Тогда стоит попробовать read-only эндпоинты -
/info, /version, /containers/json - они часто пропускаются фильтром и дают ценную информацию о соседних контейнерах.Привилегированные контейнеры и Linux capabilities
Контейнер с флагом--privileged (или privileged: true в манифесте пода) получает все Linux capabilities, доступ к устройствам хоста, отключённый seccomp-профиль и видимость всех устройств через /dev. Изоляция? Какая изоляция - её тут нет.Побег через cgroups v1 release_agent - классика. Суть: создаём cgroup, указываем
release_agent (скрипт, который выполнится на хосте при завершении всех процессов в cgroup), записываем команду и триггерим. Работает благодаря CAP_SYS_ADMIN и доступу к cgroup filesystem.Но даже без полного
--privileged отдельные capabilities опасны. CAP_SYS_ADMIN позволяет монтировать файловые системы, создавать namespaces, манипулировать cgroups - каждое из этих действий может привести к container breakout. Комбинация CAP_SYS_PTRACE + CAP_SYS_ADMIN позволяет через nsenter войти в PID namespace хоста. Даже CAP_DAC_READ_SEARCH открывает чтение произвольных файлов хоста через open_by_handle_at - на этом построен инструмент DEEPCE.По данным Red Hat (2024 Kubernetes security report), более двух третей организаций замедлили внедрение контейнеров из-за опасений безопасности, а почти половина понесли финансовые потери от инцидентов с контейнерами. Цифры говорят сами за себя.
Уязвимости container runtime: хронология и практика
Уязвимости runtime - самый критичный вектор, потому что позволяют побег даже из правильно настроенного контейнера. Тут уже не мисконфиг - тут баг в самом фундаменте.CVE-2019-5736 (runc, CVSS 8.6) - классика container escape. Уязвимость в runc до версии 1.0-rc6 (Docker до 18.09.2) позволяла перезаписать бинарник runc на хосте и получить root. Вектор: через подконтрольный образ или через
docker exec в контейнер, к которому атакующий ранее имел доступ на запись. Причина - некорректная обработка файловых дескрипторов (CWE-668). CVSS-вектор: AV:L/AC:L/PR:N/UI:R/S:C - обратите внимание на S:C (scope Changed), то есть выход за пределы контейнера на хост.CVE-2024-21626 (runc, CVSS 8.6) - более свежая. В runc 1.1.11 и ранее из-за утечки внутреннего файлового дескриптора (CWE-403, CWE-668) новый процесс через
runc exec получал рабочую директорию в файловой системе хоста. Прямой доступ к хостовой ФС - побег без привилегированного режима.CVE-2024-0132 (NVIDIA Container Toolkit, CVSS 9.0 CRITICAL) - TOCTOU-уязвимость (CWE-367) в версии 1.16.1 и ранее. По данным Wiz Research, специально сформированный образ мог получить доступ к ФС хоста. Эксплойт монтировал корневую ФС хоста внутрь контейнера, а дальше через
docker.sock - привилегированный контейнер и полная компрометация. Затрагивала Docker, containerd и CRI-O. Podman с нативной CDI-поддержкой не пострадал (CDI обходила уязвимый код). Исправлено в 1.17.4, причём первый патч оказался неполным - обход отслеживается как CVE-2025-23359 (CVSS 8.3, тот же класс TOCTOU). Два патча на одну дыру - бывает.CVE-2025-9074 (Docker Desktop, CVSS 9.3 CRITICAL) - 2025 год. Контейнеры могли обращаться к Docker Engine API через внутреннюю подсеть Docker (по умолчанию
192.168.65.7:2375) без аутентификации. Работало независимо от настроек Enhanced Container Isolation и флага «Expose daemon on tcp://localhost:2375 without TLS». Атакующий мог выполнять привилегированные команды к Engine API (CWE-668).CVE-2025-31133 (runc, CVSS 7.3) - свежая, runc до 1.2.7. При маскировке путей через
/dev/null runc не проверял, что источник bind-mount действительно настоящий inode /dev/null (CWE-61, CWE-363). Два вектора: произвольный mount-гаджет для раскрытия информации хоста и DoS.Ядерные уязвимости - отдельная категория. CVE-2022-0847 (Dirty Pipe, CVSS 7.8, CWE-665) позволяла перезаписывать данные в read-only файлах. Сама по себе это privilege escalation, но в контексте контейнеров она комбинируется: если внутри контейнера доступен бинарник runc, атакующий перезаписывает его вредоносной версией - и при следующем вызове получает выполнение на хосте. CVE-2022-0492 (CVSS 7.8, CWE-287/CWE-862) - уязвимость в
cgroup_release_agent_write ядра Linux, позволявшая через cgroups v1 release_agent обойти изоляцию namespaces.Kubernetes pentest - от пода к кластеру
Docker escape даёт root на одной ноде. Атаки на Kubernetes открывают путь к управлению всем кластером. Kubernetes добавляет собственный слой абстракции - и собственный зоопарк векторов атак.Атаки на kubelet API и незащищённый etcd
Kubelet API (порт 10250) - агент на каждой ноде, управляющий подами. По умолчанию требует аутентификацию, но на self-hosted кластерах нередко встречается--anonymous-auth=true или read-only порт 10255. Через открытый kubelet можно выполнять команды в любом поде на ноде: эндпоинт /run/<namespace>/<pod>/<container> принимает POST с произвольными командами. Прямая реализация Container Administration Command (T1609).Проверка:
curl -k https://<node-ip>:10250/pods - если в ответ приходит JSON со списком подов, kubelet открыт. Дальше: curl -k -XPOST -H "Content-Type: application/x-www-form-urlencoded" https://<node-ip>:10250/run/<namespace>/<pod>/<container> -d 'cmd=id' выполнит команду. В свежих версиях Kubernetes эндпоинт /run/ может быть отключён; альтернатива - /exec/ через WebSocket/SPDY (удобнее всего через kubeletctl). На одном из проектов я обнаружил 14 нод с открытым kubelet в корпоративном EKS-кластере - security group разрешала доступ со всей внутренней подсети. Четырнадцать. В EKS.Etcd (порт 2379) - распределённая key-value база, где Kubernetes хранит всё состояние кластера: конфигурации, секреты, токены service accounts. По умолчанию etcd требует TLS-аутентификацию клиентскими сертификатами, но эту настройку иногда отключают. Если etcd доступен без аутентификации, одна команда
etcdctl get / --prefix --keys-only покажет все ключи кластера, а etcdctl get /registry/secrets/<namespace>/<secret-name> - содержимое секрета в открытом виде (если не включено шифрование at-rest). Техника Container API (T1552.007, Credential Access).RBAC misconfiguration и эскалация привилегий в k8s
Role-Based Access Control - основной механизм авторизации в Kubernetes. На практике мисконфигурации RBAC - главный вектор эскалации привилегий в k8s. Проблема стара как мир: для быстрого решения проблем администраторы выдают избыточные права service accounts. «Потом поправим». Не поправят.Первым делом после получения доступа к поду проверяем возможности нашего service account:
kubectl auth can-i --list покажет полный список разрешений. Если в ответе есть create pods, create deployments или (джекпот) get secrets - можно двигаться дальше.Наиболее опасные комбинации RBAC-прав, которые я эксплуатировал на реальных проектах:
| Право | Что даёт атакующему | Техника MITRE ATT&CK |
|---|---|---|
create pods | Создание привилегированного пода с hostPath-монтированием | Deploy Container (T1610) |
create daemonsets | Развёртывание пода на каждой ноде кластера одновременно | Deploy Container (T1610) |
get secrets | Чтение всех секретов namespace, включая токены других SA | Container API (T1552.007) |
create clusterrolebindings | Привязка cluster-admin к своему SA | Additional Container Cluster Roles (T1098.006) |
patch pods | Инъекция контейнера в существующий под | Container Administration Command (T1609) |
create serviceaccounts/token | Генерация токенов для любого SA в namespace | Container API (T1552.007) |
Самый быстрый путь к cluster-admin: если SA имеет право
create clusterrolebindings, выполняем kubectl create clusterrolebinding pwned --clusterrole=cluster-admin --serviceaccount=<namespace>:<sa-name> - мгновенно получаем полный контроль. Техника Additional Container Cluster Roles (T1098.006, Persistence/Privilege Escalation). Одна команда - и кластер ваш.Если прав на создание clusterrolebindings нет, но есть
create pods - создаём под с hostPID: true, hostNetwork: true и hostPath монтированием / в /host, после чего nsenter -t 1 -m -u -i -n -p -- bash для входа в PID namespace хоста. Классический Escape to Host (T1611).Lateral movement через Kubernetes-примитивы
После первоначальной эскалации Kubernetes сам становится инструментом для lateral movement. Забудьте про SSH-туннели - атакующий использует нативные механизмы оркестратора, и это красиво (с точки зрения атакующего, конечно).Service account tokens в Kubernetes до версии 1.24 по умолчанию монтировались в каждый под как долгоживущие секреты. Даже в более новых версиях с bound tokens часто остаются legacy-конфигурации. Каждый скомпрометированный под - потенциальный источник нового токена с другими правами. Через
kubectl get serviceaccounts -A с достаточными правами можно найти SA с привилегиями в других namespace, создать для них токены и двигаться латерально по кластеру.ConfigMaps и Secrets часто содержат credentials к внешним системам: базам данных, облачным API, CI/CD. Команда
kubectl get secrets -A -o json на кластере со слабым RBAC выгружает все секреты всех namespace разом. На одном из проектов я вытянул таким образом AWS IAM credentials, которые дали доступ к S3-бакетам с бэкапами продуктивных баз данных. Бэкапы. Продуктивных. Баз.DaemonSets - объект Kubernetes, который гарантирует запуск пода на каждой ноде кластера. Для атакующего это механизм массового развёртывания: один манифест - и ваш код работает на всех нодах одновременно. На практике используется как для закрепления (persistence), так и для криптомайнинга - Compute Hijacking (T1496.001).
Инструменты для пентеста контейнеров Docker и Kubernetes
Полноценный пентест контейнерной инфраструктуры требует специализированного инструментария. Вот что я использую и в каком порядке:CDK (Container Penetration Toolkit) - основной инструмент для работы изнутри скомпрометированного контейнера. Один статический бинарник, никаких зависимостей. Команда
./cdk evaluate автоматически проверяет: доступен ли Docker socket, какие capabilities, есть ли "чувствительные монтирования", доступен ли kubelet и K8s API. Для побега - ./cdk run <exploit-name> поддерживает docker-sock-escape, mount-cgroup, service-account-check и другие. Лично у меня это первое, что загружается в контейнер после получения шелла.kube-hunter от Aqua Security - сканер уязвимостей в Kubernetes. Три режима: удалённо (по IP), изнутри пода и active hunting (пытается эксплуатировать найденное). На проектах запускаю его из скомпрометированного пода в active-режиме - автоматически находит открытые kubelet, незащищённый etcd и мисконфигурации API-сервера.
Trivy - для анализа образов на известные CVE. Если есть доступ к приватному Docker Registry (порт 5000), скачиваю образы и прогоняю через Trivy: часто нахожу уязвимые версии runtime-компонентов или забытые секреты в слоях образа.
Peirates - постэксплуатация в Kubernetes. Автоматизирует сбор secrets с нод, перебор service accounts, проверку RBAC-прав. Незаменим, когда нужно быстро оценить масштаб компрометации в большом кластере.
kubesploit (CyberArk) - пост-эксплуатационный фреймворк, что-то вроде Metasploit для контейнерных сред. Модули для container escape, lateral movement и persistence в Kubernetes.
Для аудита конфигурации (когда нужно показать заказчику масштаб проблем): kube-bench (проверка CIS Kubernetes Benchmark) и kubescape (линтер YAML-манифестов на безопасность).
Безопасность Kubernetes и Docker - что реально останавливает пентестера
За годы работы я составил свой список защитных мер, которые действительно осложняют жизнь атакующему - не «галочки в чеклисте», а реальные препятствия.Pod Security Standards / Pod Security Admission (замена устаревших PodSecurityPolicies) в режиме enforce с профилем
restricted - реально блокирует создание привилегированных подов, запуск от root и монтирование hostPath. На проектах, где это настроено корректно, путь от пода к ноде через Kubernetes-примитивы закрыт. Я упирался в эту стену несколько раз - приходилось искать обходные пути через runtime-CVE.Network Policies - по умолчанию в Kubernetes все поды общаются друг с другом. Грамотные Network Policies с deny-all default и точечными разрешениями резко ограничивают lateral movement. Без них атакующий из одного пода через внутреннюю сеть кластера добирается до etcd, kubelet и API-сервера.
Минимальный securityContext для каждого контейнера -
runAsNonRoot: true, allowPrivilegeEscalation: false, readOnlyRootFilesystem: true, capabilities: drop: ALL с добавлением только необходимых. По данным Aikido Security, именно такая конфигурация предотвращает эксплуатацию CVE-2022-0492 даже на непатченных ядрах.
YAML:
# securityContext, который реально останавливает container breakout
securityContext:
runAsUser: 1000
runAsNonRoot: true
allowPrivilegeEscalation: false
readOnlyRootFilesystem: true
capabilities:
drop: ["ALL"]
add: ["NET_BIND_SERVICE"]
seccompProfile:
type: RuntimeDefault
gVisor и Kata Containers - hardened runtimes с дополнительным слоем изоляции. gVisor перехватывает системные вызовы контейнера через собственное ядро (Sentry), Kata запускает каждый контейнер в лёгкой виртуальной машине. Container escape через ядерные уязвимости вроде Dirty Pipe становится бесполезным - атакующий попадает в ядро gVisor/Kata VM, а не хоста. Ощущение, как будто ломишь стену, а за ней ещё одна стена.
Своевременный патчинг runtime - банально, но критически важно. Между публикацией CVE-2024-21626 и массовым обновлением runc на продуктивных кластерах прошли месяцы. Всё это время среды были уязвимы. Мониторинг версий через Trivy и автоматизация обновлений - необходимость, а не рекомендация.
И наконец - не монтируйте Docker socket в контейнеры. Если CI/CD требует сборки образов - используйте Kaniko (сборка без Docker daemon) или rootless Docker-in-Docker. Каждый смонтированный docker.sock на пентесте превращается в container breakout за 30 секунд. Тридцать. Секунд.
Чеклист для пентеста контейнерной инфраструктуры
Пентест контейнеров Docker и Kubernetes - последовательное движение по цепочке, где каждый этап опирается на предыдущий:- Разведка - обнаружение Docker API, K8s API, kubelet, etcd, Registry (T1613)
- Начальный доступ - RCE в приложении, утёкшие credentials, открытый API
- Ориентирование - определение контекста: capabilities, volumes, service account, namespace
- Container breakout - Docker socket, привилегированный контейнер, CVE runtime (T1611)
- Credential access - извлечение SA tokens, secrets, etcd credentials (T1552.007)
- Privilege escalation - RBAC abuse, clusterrolebinding, ядерные CVE (T1068, T1098.006)
- Lateral movement - перемещение между namespace через tokens, DaemonSet-развёртывание
- Захват кластера - получение cluster-admin, доступ ко всем нодам и секретам
create pods или одного непатченного runc, чтобы цепочка дошла до финала.Проверьте свои кластеры:
kubectl auth can-i --list --as=system:serviceaccount:default:default - если в ответе что-то кроме get на базовые ресурсы, у вас та же проблема, что и у большинства.
Последнее редактирование модератором: