Незащищённый Docker Registry: уязвимость CI/CD, через которую утекают секреты за 10 минут

На аудите CI/CD-инфраструктуры для финтех-компании я нашёл Docker Registry на порту 5000 без какой-либо аутентификации. Десять минут с curl и docker save — и на экране AWS-ключи, токен GitLab CI и пароль от продакшен-базы, зашитые прямо в слоях контейнерных образов. Пайплайн работал, тесты зелёные, деплой шёл автоматически. При этом любой, кто знал IP-адрес сервера, мог скачать все образы и вытащить из них критические секреты.
Ниже — полный разбор цепочки: от обнаружения открытого реестра до компрометации облачной инфраструктуры. И конкретный чек-лист защиты, который я оставляю клиентам после каждого такого аудита.
Docker Registry без аутентификации — как возникает уязвимость пайплайна
Docker Registry — сервис, который хранит и раздаёт контейнерные образы. Образ — это готовый пакет с приложением и всеми его зависимостями, упакованный для запуска в контейнере. Когда CI/CD-пайплайн (автоматизированная цепочка сборки, тестирования и деплоя кода) собирает приложение, результат в виде Docker-образа отправляется в реестр. Оттуда его забирают серверы для запуска в продакшене.
Проблема — в том, как этот реестр разворачивается. Стандартный Docker Registry v2 по умолчанию стартует без аутентификации. Одна команда docker run -d -p 5000:5000 registry:2 — и реестр слушает на порту 5000, открытый любому в сети. В документации Docker аутентификация вынесена в отдельный раздел конфигурации: htpasswd-файл, TLS-сертификат, интеграция с внешним auth-сервером. Ничего из этого не включено «из коробки».
Сценарий повторяется из проекта в проект. DevOps-инженер поднимает реестр для внутреннего пайплайна и планирует «потом» настроить TLS и авторизацию. «Потом» не наступает — реестр работает, билды идут, бизнес доволен. Через полгода кто-то пробрасывает порт наружу. Или порт был открыт с самого начала, но никто не проверял.
Масштаб подтверждается цифрами: по данным Trend Micro, при одном сканировании обнаружено 197 уникальных открытых реестров и выгружено 20 503 образа общим объёмом 9,31 ТБ. Исследование Flare за один месяц нашло более 10 000 образов на Docker Hub с утечками production-credentials — включая секреты компаний из Fortune 500.
Это не разовая ошибка одного джуниора — это системная проблема конфигурации. В классификации OWASP Top 10 (список десяти самых критичных веб-уязвимостей, обновляется раз в несколько лет) она попадает сразу под две категории: A05:2021 Security Misconfiguration — небезопасные настройки по умолчанию, и A07:2021 Identification and Authentication Failures — отсутствие аутентификации на критическом сервисе.
Русскоязычные источники подробно разбирают сканирование образов на уязвимости (Trivy, Clair, Grype) и безопасность Dockerfile, но почти не описывают атаку на сам реестр — как он обнаруживается, как эксплуатируется и что из него можно вытащить. Этот пробел я здесь и закрываю.
Поиск открытых Docker Registry — разведка перед атакой
Прежде чем эксплуатировать реестр, его нужно найти. В терминах MITRE ATT&CK (открытая база тактик и техник атак — каждая техника обозначается T-кодом, тактика группирует техники по цели атакующего) обнаружение реестра сочетает Active Scanning (T1595, тактика Reconnaissance) и Network Service Discovery (T1046, тактика Discovery).
Действия атакующего просты. Docker Registry v2 слушает на порту 5000 (реже 443 или другом) и отвечает на стандартные HTTP-запросы. Проверить доступность реестра можно одним запросом: curl -s http://TARGET:5000/v2/. Если в ответ приходит {} — реестр открыт без аутентификации. Код 401 Unauthorized — авторизация настроена, идём дальше.
Для массового поиска по интернету есть специализированные поисковики:
Shodan — запрос "Docker-Distribution-Api-Version" port:5000 находит серверы, которые в HTTP-заголовках объявляют себя Docker Registry. Censys работает аналогично — поиск по баннерам и сертификатам TLS. Оба индексируют миллионы IP-адресов и возвращают результаты за секунды.
Nmap — сетевой сканер для целевой разведки конкретной подсети на внутреннем пентесте. Команда nmap -p 5000 --open -sV 10.0.0.0/24 покажет все хосты с открытым портом 5000 и попытается определить версию сервиса. Если в результатах Docker Registry (API: 2.0) — цель найдена.
На внутренних пентестах открытые реестры чаще всего обнаруживаются в dev- и staging-сегментах. Их создавали «для себя», и они никогда не проходили security review. Managed-решения вроде Harbor, GitLab Container Registry или облачных реестров (AWS ECR, GCR, Azure ACR) по умолчанию требуют аутентификацию. Но голый registry:2 разворачивается именно потому, что он проще и не требует ничего, кроме Docker.
Утечка секретов сборки: пошаговая эксплуатация незащищённого реестра
Реестр найден и отвечает без авторизации — начинается эксплуатация. В MITRE ATT&CK это Exploit Public-Facing Application (T1190, тактика Initial Access): доступ через публично доступный сервис без подбора паролей и без эксплойтов. Сервис просто открыт.
Ниже — пошаговая последовательность с объяснением каждого шага.
Предусловия: компьютер с curl, docker и tar. Сетевой доступ к целевому хосту. На Kali Linux всё перечисленное стоит из коробки.
Перечисление репозиториев через Registry API v2
Первый шаг — узнать, какие образы хранятся в реестре. Docker Registry HTTP API v2 (REST-интерфейс для управления реестром) предоставляет эндпоинт _catalog, который возвращает полный список репозиториев.
# Запрос списка всех репозиториев
curl -s http://TARGET:5000/v2/_catalog
# Ожидаемый ответ: {"repositories":["app-backend","payment-service"]}
# Для конкретного репозитория — список тегов (версий образа)
curl -s http://TARGET:5000/v2/payment-service/tags/list
# Ожидаемый ответ: {"name":"payment-service","tags":["latest","v2.1"]}
Если _catalog вернул JSON со списком — реестр полностью открыт на чтение. Имена репозиториев часто говорят за себя: payment-service, auth-api, internal-dashboard. Такие названия подсказывают, где искать секреты в первую очередь.
Для подробностей об образе запрашиваем манифест: curl -s http://TARGET:5000/v2/payment-service/manifests/latest. Он покажет список слоёв, из которых состоит образ, и метаданные сборки. Иногда секреты видны уже здесь — прямо в переменных окружения из конфигурации образа.
Извлечение CI/CD секретов из слоёв образа
Следующий шаг — скачать образ и найти в нём чувствительные данные. В MITRE ATT&CK это Credentials In Files (T1552.001, тактика Credential Access) — поиск учётных данных, сохранённых в файлах.
Образ скачивается командой docker pull TARGET:5000/payment-service:latest. Дальше два метода анализа.
Метод А — runtime-анализ. Запустить контейнер через docker run -it --rm TARGET:5000/payment-service:latest /bin/sh и осмотреться внутри: ls -la, cat .env, env. Работает быстро, но образ может не запуститься из-за несовпадения архитектуры (ARM vs x86) или недостающих зависимостей.
Метод Б — статический анализ. Работает всегда, потому что не требует запуска контейнера:
# Сохранить образ как tar-архив
docker save TARGET:5000/payment-service:latest -o service.tar
# Распаковать (каждый слой образа — отдельный каталог)
mkdir layers && tar -xf service.tar -C layers
# Искать секреты через все слои по шаблонам
grep -rl "AWS_SECRET\|PRIVATE KEY\|password=" layers/
Если grep вернул пути к файлам — в них лежат секреты. Типичное содержимое найденного .env-файла (конфигурационного файла с переменными окружения):
AWS_ACCESS_KEY_ID=AKIAYO5AK3xxxxxxxxxx
AWS_SECRET_ACCESS_KEY=DjD71wF1xxxxxxxxxxxx
DB_PASSWORD=pr0d_s3cret_2024
GITLAB_TOKEN=glpat-xxxxxxxxxxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxx
По данным Trend Micro, среди файлов с чувствительными данными в открытых реестрах чаще всего встречаются: .env с ключами API и паролями баз данных, Dockerfile с секретами в директивах ARG/ENV, конфигурации CI/CD-систем (.gitlab-ci.yml, Jenkinsfile, azure-pipelines.yml) с токенами доступа, SSH-ключи id_rsa и конфигурации пакетных менеджеров (.npmrc с токенами для приватных npm-пакетов).
Исследование Intrinsec показало: из случайной выборки публичных Docker-образов примерно 14% содержат данные, которые не должны быть доступны публично — учётные данные, приватные ключи, API-токены и персональную информацию пользователей. Каждый седьмой образ. Вдумайтесь.
От утечки CI/CD секретов к компрометации облака
Самое опасное начинается после извлечения секретов. Допустим, в .env лежат AWS_ACCESS_KEY_ID и AWS_SECRET_ACCESS_KEY. Атакующий настраивает AWS CLI через aws configure --profile stolen, указывает найденные ключи и проверяет их командой aws sts get-caller-identity --profile stolen. Если команда вернула ARN (Amazon Resource Name — уникальный идентификатор ресурса в AWS) — ключи действительны. Атакующий аутентифицирован в облаке компании.
В MITRE ATT&CK это Valid Accounts (T1078) — использование легитимных учётных данных вместо взлома. По данным IBM X-Force, за 2024 год рост атак с использованием действительных учётных данных составил 71%. Атакующие не взламывают — они входят по ключу, который компания сама оставила в открытом доступе.
Дальше — разведка привилегий: aws s3 ls покажет доступные S3-бакеты (облачные хранилища файлов), aws iam list-attached-user-policies покажет права скомпрометированного пользователя. По данным Flare, в 42% случаев один скомпрометированный образ содержал пять и более различных секретов — один образ способен открыть доступ сразу к нескольким облачным сервисам, репозиториям кода и внутренним системам.
И ещё хуже: по данным того же исследования, 75% разработчиков, удалявших секреты из контейнеров после обнаружения утечки, не отзывали и не ротировали сами ключи. Удалённый из образа секрет навсегда остаётся в истории слоёв. И в руках атакующего.
Supply chain атака DevOps — бизнес-логика угрозы
Зачем атакующему открытый Docker Registry? Мотивация выходит далеко за пределы кражи одного пароля.
Открытый реестр — точка входа для supply chain атаки (атаки на цепочку поставки ПО). Если реестр доступен не только на чтение, но и на запись — а при отсутствии аутентификации это частый случай — атакующий может подменить образ.
Сценарий: злоумышленник скачивает payment-service:latest, внедряет бэкдор, заливает образ обратно через docker push под тем же тегом. Следующий деплой из CI/CD-пайплайна заберёт модифицированный образ и развернёт его в продакшене. Ни один функциональный тест не сработает — приложение работает как прежде, просто теперь в нём есть код атакующего.
По данным CrowdStrike, количество cloud intrusion-инцидентов выросло на 26% год к году за 2024 год. Docker Registry без аутентификации — одновременно источник действительных учётных данных и точка для модификации кода, который попадёт в продакшен.
Если после получения доступа к контейнеру атакующий обнаружит уязвимое ядро, возможен container escape — выход из контейнера на хост-систему (Escape to Host, T1611, тактика Privilege Escalation). Пример — CVE-2022-0847, она же DirtyPipe: уязвимость ядра Linux версий от 5.8 до 5.16.11 (CWE-908 — Use of Uninitialized Resource).
CVSS-оценка (стандартизированная шкала критичности уязвимости от 0 до 10) — 7.8 по вектору CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H. Расшифровка: AV:L — атака локальная (нужен доступ к системе), AC:L — низкая сложность, PR:L — достаточно обычного пользователя, UI:N — без участия жертвы. EPSS-оценка (Exploit Prediction Scoring System — вероятность эксплуатации уязвимости в ближайшие 30 дней) — 0.90, это топ-1% среди всех CVE. Уязвимость включена в каталог CISA KEV — список уязвимостей, которые уже эксплуатируются в реальных атаках — с решением SSVC «Act» (патчить немедленно). Публичные PoC для container escape через DirtyPipe есть на GitHub, например DataDog/dirtypipe-container-breakout-poc (77 звёзд).
Полная цепочка: открытый реестр (T1190) → извлечение секретов из слоёв (T1552.001) → аутентификация в облаке украденными ключами (T1078) → подмена образа → container escape (T1611) → контроль хоста. Supply chain атака, собранная из стандартных техник без единого zero-day.
Безопасность CI/CD пайплайна: чек-лист защиты от docker registry misconfiguration
Защита начинается с простого — не оставлять реестр открытым. Ниже — чек-лист, который я оставляю каждому клиенту после аудита.
Аутентификация и сетевой доступ:
- Настроить basic auth или token-based аутентификацию для Registry API. Docker Registry поддерживает htpasswd-файл и внешние authorization-серверы
- Закрыть порт реестра от публичного доступа. Реестр должен быть доступен только из CI/CD-сети через VPN или приватную подсеть
- Включить TLS: реестр должен работать через HTTPS. Без TLS даже настроенная аутентификация бесполезна — пароли летят по сети открытым текстом
- Рассмотреть managed-решения (Harbor, GitLab Container Registry, облачные реестры AWS ECR / GCR / Azure ACR) вместо голого
registry:2— аутентификация и TLS включены по умолчанию
Управление секретами в контейнерах:
- Не зашивать секреты в
DockerfileчерезARGилиENV. Использовать Docker BuildKit secrets (--mount=type=secret) или секрет-менеджеры: HashiCorp Vault, AWS Secrets Manager, Kubernetes Secrets - Добавить
.env,.git,id_rsa,*.pem,.npmrcв.dockerignore— это предотвращает случайное копирование чувствительных файлов в образ приCOPY . . - Использовать multi-stage builds (многоступенчатая сборка): секреты и инструменты сборки остаются в промежуточном слое и не попадают в финальный образ, который уходит в реестр
Сканирование и supply chain security контейнеризация:
- Встроить сканирование образов в пайплайн. Trivy или Grype находят и уязвимые пакеты, и зашитые секреты за секунды
- Подписывать образы через Docker Content Trust (DCT) или Cosign/Sigstore — это гарантирует, что в продакшен попадёт образ, который собрал CI/CD, а не подменённый атакующим
- Проверять сети на открытые реестры регулярно:
nmap -p 5000,5001 --openпо внутренним подсетям раз в неделю — дешёвый и рабочий контроль
Минимизация blast radius (радиус поражения):
- Ротировать все секреты, которые когда-либо попадали в Docker-образ. Удалить секрет из текущей версии недостаточно — он остаётся в истории слоёв предыдущих сборок
- Применять принцип наименьших привилегий для облачных ключей: AWS-ключ, используемый в приложении, не должен иметь
AdministratorAccess— только те permissions, которые нужны для работы - Запускать контейнеры от непривилегированного пользователя (
USER nonrootв Dockerfile) и без лишних Linux capabilities — это существенно затрудняет container escape даже при наличии уязвимости ядра
За два года я провёл аудит CI/CD-инфраструктуры в восьми компаниях — от стартапов до организаций с тысячами контейнеров в продакшене. В пяти из восьми Docker Registry был доступен без аутентификации хотя бы из одного сегмента сети. В трёх из пяти оттуда удавалось извлечь действующие облачные ключи.
Реакция команды каждый раз одинаковая: «Мы знали, что надо настроить авторизацию, но руки не дошли».
Это не проблема знаний. Средства DevSecOps-безопасности контейнеров бесплатны и документированы. Это проблема приоритизации, где безопасность реестра проигрывает каждой следующей бизнес-фиче в бэклоге. CI/CD-пайплайн — самая недооценённая поверхность атаки в современной инфраструктуре: по определению у него есть доступ ко всему — к исходному коду, секретам, продакшен-серверам и облачным аккаунтам. Одна docker registry misconfiguration — и этот доступ получает любой желающий.
Пока DevOps-культура в компании не включает security review на этапе «поднял сервис — проверь доступ», открытые реестры будут появляться снова. Базовый трек IB Basics — это то, что я бы прошёл вместо самостоятельного блуждания по YouTube, если бы начинал путь в ИБ сейчас.
Эту тему и смежные навыки разбирают на практике в курсе «Основы практик DevOps» Codeby Academy.