Якщо ви колись виконували docker ps на сервері і відчували себе відповідальною дорослою людиною, ось несподіваний поворот:
ті самі можливості можуть стати доступні через відкритий інтернет через одну необачну зміну конфігурації. Без експлойту. Без хитромудрих payload-ів.
Просто «привіт, daemon» — і ваш хост стає чужим обчислювальним ресурсом.
Docker Remote API — це не «ще один сервісний порт». Це панель керування на рівні root, яка може запускати контейнери з привілеями,
монтувати файлову систему хоста і непомітно впроваджуватися. Ставтеся до нього як до SSH з паролем і публічною IP-адресою — бо функціонально це ще гірше.
Чому Docker Remote API фактично рівнозначний root
Демон Docker (dockerd) — це авторитет, який створює неймспейси, налаштовує cgroups, монтує файлові системи,
керує мережами контейнерів і запускає процеси від вашого імені. Керуючи демоном, ви контролюєте хост.
Remote API (через Unix-сокет або TCP) — це не «інтерфейс для адміністрування» типу лише читання. Це кермо руля.
Ось основна проблема: демон виконує привілейовані дії і довіряє запитам, що надходять на його API-ендпоїнт.
Якщо цей ендпоїнт доступний і не має жорсткої автентифікації, будь-хто, хто може з ним спілкуватися, може:
- Запустити контейнер з
--privileged - Зробити bind-монтування
/хоста всередину контейнера - Записати SSH-ключі в
/root/.ssh/authorized_keys - Встановити персистентність через systemd-юнити, cron або drop-in сервіси
- Екфільтрувати секрети з environment, томів і шарів образів
- Пивотувати у вашу мережу, використовуючи маршрутизацію й облікові дані хоста
Саме тому інциденти «Docker API відкритий» часто пропускають фазу експлойту і переходять прямо до монетизації: майнінг,
ботнети, крадіжка облікових даних або латеральний рух. Їм не потрібно ламати захист. Ви відчинили двері і залишили там записку «admin всередині».
Жарт №1: Відкривати порт 2375 в інтернет — це як класти ключ під килимок і транслювати килимок в прямому ефірі.
Факти та історичний контекст, які варто знати
Це короткі, конкретні факти, які мають значення, бо пояснюють, чому проблема повторюється.
Частина — історія, частина — «як ми сюди потрапили», усі вони з’являються у постмортемах.
- За замовчуванням демон Docker слухає Unix-сокет (
/var/run/docker.sock), а не TCP, саме щоб уникнути віддаленого відкриття за замовчуванням. - Порт 2375 умовно означає «Docker по TCP без TLS»; порт 2376 — «Docker по TCP з TLS». Багато сканерів шукають 2375 насамперед.
- Ранні інструменти Docker нормалізували віддалені демони (особливо в CI і робочих процесах «Docker-in-Docker»), і ці звички залишилися, навіть коли змінилися моделі загроз.
- Docker Machine популяризував віддалені Docker-ендпоїнти для провізіонування хостів; багато старих статей досі показують небезпечні шаблони, що перейшли в сучасні кластери.
- Remote API базується на HTTP. Якщо ви його досягаєте — говорити з ним можна через
curl. Це зручно для автоматизації і катастрофічно для відкритих мереж. - Група «docker» фактично рівнозначна root на більшості систем, бо дає доступ до сокета демона. Це не тонкість; це часто ігнорують.
- Зловмисники давно автоматизували сканування на відкриті Docker. Це не нішова загроза; це фонове випромінювання на кшталт SSH brute-force.
- Правила безпеки в хмарах і фаєрволи змінювалися з часом. «Тимчасове» правило іноді стає постійним, бо ніхто не відповідає за прибирання.
Модель загроз: як зловмисники використовують відкритий daemon
Звичайний kill chain смішно простий
Коли Docker слухає на tcp://0.0.0.0:2375 (або на публічному інтерфейсі) без TLS-клієнтської автентифікації,
робота зловмисника виглядає приблизно так:
- Знайти IP з відкритим 2375.
- Викликати API, щоб перелічити контейнери/образи.
- Запустити контейнер з bind-монтуванням кореня хоста.
- Змінити файли хоста, щоб зберегти доступ.
- Опціонально: запустити майнер у контейнері і піти.
Чому контейнери не рятують вас від контролю демона
Контейнери — це примітиви ізоляції, а не магічний захист. Межа ізоляції забезпечується ядром, але сутність, що налаштовує
цю межу — це демон. Якщо ви можете сказати демону «змонтируй корінь хоста в цей контейнер», ядро виконає запит, бо демон має на це право.
А як щодо «rootless Docker»?
Rootless Docker зменшує радіус ураження, але не робить відкритий API нешкідливим. Він все ще дає зловмиснику можливість запускати довільні
робочі навантаження від імені того користувача, доступ до його секретів і можливість пивотувати. Rootless допомагає; це не картка «вільний прохід».
Одна цитата, яку варто запам’ятати
«Надія — не стратегія.»
Ставтеся до «ми не думаємо, що він відкритий» як до надії, а не плану.
Швидкий план діагностики
Ви на виклику. Хтось каже: «Чому цей хост плавиться?» Або гірше: «Чому ми маємо вихідний трафік в дивні місця?»
Не починайте з дебатів про філософію контейнерів. Почніть з пошуку контролю-плейну і фактичного навантаження.
Перше: чи демон доступний по TCP?
- Перевірте прослуховувані порти (
ss/lsof) і прапори сервісу Docker. - Перевірте стан фаєрволу/security group, а не лише локальну конфігурацію.
- Якщо бачите
0.0.0.0:2375або:::2375, вважайте це інцидентом поки не доведено протилежне.
Друге: чи є ознаки несанкціонованих контейнерів або образів?
- Перелік запущених контейнерів; шукайте майнери, дивні імена образів, контейнери з
--privileged, монтування хоста або підозрілі мережеві налаштування. - Перегляньте часові мітки створення контейнерів.
- Перевірте на нових користувачів/ssh-ключі/systemd-юнити, створені процесами з контейнерів, які писали в хост.
Третє: ізолюйте, потім розслідуйте
- Негайно заблокуйте вхідний доступ до демона на краю мережі.
- Зробіть знімки доказів (списки процесів, метадані контейнерів, логи) перед тим, як щось видаляти.
- Ротація облікових даних, до яких могли отримати доступ: ролі інстансів у хмарі, облікові дані реєстру, секрети додатків на диску.
Практичні завдання: виявити, підтвердити та вирішити (команди включені)
Це практичні завдання, які можна виконати на хості. Кожне містить, що означає вивід і яке рішення прийняти.
Ніякої магії, ніякого «перевірте свій фаєрвол». Ви — фаєрвол зараз.
Завдання 1: Перевірити, чи Docker слухає на TCP
cr0x@server:~$ sudo ss -lntp | grep -E ':(2375|2376)\s'
LISTEN 0 4096 0.0.0.0:2375 0.0.0.0:* users:(("dockerd",pid=1024,fd=6))
Що це означає: dockerd слухає на всіх IPv4-інтерфейсах на 2375 у відкритому вигляді.
Рішення: вважайте це критичним витоком. Перейдіть до ізоляції: блокуйте на фаєрволі і переконфігуруйте демон.
Завдання 2: Підтвердити конфігурацію демона через systemd
cr0x@server:~$ systemctl cat docker | sed -n '1,120p'
# /lib/systemd/system/docker.service
[Service]
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock -H tcp://0.0.0.0:2375
Що це означає: Демон явно запущено з TCP-лістенером. Це не випадкова поведінка ядра; це налаштування.
Рішення: видаліть TCP-host, якщо ви не впроваджуєте взаємний TLS і жорсткі мережеві обмеження.
Завдання 3: Перевірити docker info для налаштованих хостів і опцій безпеки
cr0x@server:~$ docker info | sed -n '1,80p'
Client:
Context: default
Debug Mode: false
Server:
Containers: 12
Running: 3
Paused: 0
Stopped: 9
Server Version: 26.1.0
Storage Driver: overlay2
Security Options:
apparmor
seccomp
Profile: builtin
Що це означає: Це не дає прямих відомостей про bind-адреси демона, але показує базовий профіль безпеки.
Рішення: якщо пізніше знайдете несанкціоновані контейнери, корисно знати, чи діяли AppArmor/seccomp (і чи обійшов це --privileged).
Завдання 4: З’ясувати, як зв’язаний демон (daemon.json)
cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2375"]
}
Що це означає: Remote API через plaintext TCP налаштований постійно.
Рішення: видаліть запис TCP-host або перейдіть на TLS на 2376 з автентифікацією клієнтських сертифікатів.
Завдання 5: Перевірити стан фаєрволу на хості (приклад UFW)
cr0x@server:~$ sudo ufw status verbose
Status: active
Logging: on (low)
Default: deny (incoming), allow (outgoing), disabled (routed)
To Action From
22/tcp ALLOW IN 203.0.113.0/24
2375/tcp ALLOW IN Anywhere
Що це означає: Ви «за замовчуванням відмовляєте», а потім дозволили порт, який може передати ключі.
Рішення: видаліть це правило негайно, якщо тільки воно не обмежено строгою мережею управління з TLS.
Завдання 6: Перевірити зовнішню доступність (з використанням іншої машини)
cr0x@server:~$ curl -s http://198.51.100.10:2375/version
{"Platform":{"Name":"Docker Engine - Community"},"Components":[{"Name":"Engine","Version":"26.1.0","Details":{"ApiVersion":"1.45"}}],"ApiVersion":"1.45","MinAPIVersion":"1.24","GitCommit":"...","GoVersion":"go1.22.2","Os":"linux","Arch":"amd64","KernelVersion":"6.8.0-41-generic","BuildTime":"..."}
Що це означає: Якщо ви можете без автентифікації отримати /version, зможе будь-хто.
Рішення: це інцидент, а не «звичайна заявка». Спочатку ізолюйте; потім розслідуйте.
Завдання 7: Перелічити контейнери через віддалений API (це не повинно працювати анонімно)
cr0x@server:~$ curl -s http://198.51.100.10:2375/containers/json?all=1 | head
[{"Id":"b1c...","Names":["/web-1"],"Image":"nginx:alpine","State":"running","Status":"Up 3 days"},
{"Id":"f9a...","Names":["/xmrig"],"Image":"unknown:latest","State":"running","Status":"Up 2 hours"}]
Що це означає: Віддалена анонімна інвентаризація. Наявність підозрілого імені контейнера/образу — тривожний сигнал.
Рішення: починайте збір доказів і ізоляцію. Не видаляйте просто так, поки не зафіксуєте метадані та логи.
Завдання 8: Проінспектувати підозрілий контейнер на монтування хоста і привілеї
cr0x@server:~$ docker inspect xmrig --format '{{.HostConfig.Privileged}} {{json .Mounts}}'
true [{"Type":"bind","Source":"/","Destination":"/host","Mode":"rw","RW":true,"Propagation":"rprivate"}]
Що це означає: Контейнер привілейований і має корінь хоста змонтований для запису у /host.
Рішення: вважайте хост скомпрометованим. Плануйте відновлення/перебудову, ротацію облікових даних і повний аудит.
Завдання 9: Перевірити нещодавно створені контейнери (підказка по часовій шкалі)
cr0x@server:~$ docker ps -a --format 'table {{.ID}}\t{{.Image}}\t{{.Names}}\t{{.CreatedAt}}\t{{.Status}}' | head -n 10
CONTAINER ID IMAGE NAMES CREATED AT STATUS
f9a2d1c3ab11 unknown:latest xmrig 2026-01-03 01:12:44 +0000 Up 2 hours
b1c9aa88d220 nginx:alpine web-1 2025-12-30 09:01:02 +0000 Up 3 days
Що це означає: Нещодавно з’явився новий контейнер. Якщо ніхто не може пояснити, він несанкціонований, поки не доведено інше.
Рішення: зіставте з логами (dockerd, auditd, мережеві логи хмари) і ізолюйте.
Завдання 10: Переглянути логи демона Docker на предмет доступу через Remote API
cr0x@server:~$ sudo journalctl -u docker --since "6 hours ago" | tail -n 20
time="2026-01-03T01:12:43.991Z" level=info msg="API listen on [::]:2375"
time="2026-01-03T01:12:44.120Z" level=info msg="POST /v1.45/containers/create"
time="2026-01-03T01:12:44.348Z" level=info msg="POST /v1.45/containers/f9a2d1c3ab11/start"
Що це означає: Маєте часові записи створення контейнерів через API.
Рішення: збережіть логи. Якщо є централізоване логування — переконайтеся у збереженні і експорті для інцидент-рев’ю.
Завдання 11: Перевірити, хто має доступ до Docker-сокета локально
cr0x@server:~$ ls -l /var/run/docker.sock
srw-rw---- 1 root docker 0 Jan 3 00:01 /var/run/docker.sock
Що це означає: Члени групи docker можуть контролювати демон.
Рішення: тримайте членство в групі docker мінімальним і перевіряйте його; ставтеся до нього як до sudo.
Завдання 12: Перелічити членів групи docker
cr0x@server:~$ getent group docker
docker:x:999:ci-runner,alice
Що це означає: Двоє користувачів фактично мають root-еквівалентний контроль через Docker.
Рішення: якщо немає вагомої причини, видаліть людей з цієї групи і змусьте привілейовані операції проходити через контрольовану автоматизацію.
Завдання 13: Негайна ізоляція — заблокувати TCP 2375 локально (iptables)
cr0x@server:~$ sudo iptables -I INPUT -p tcp --dport 2375 -j DROP
cr0x@server:~$ sudo iptables -L INPUT -n --line-numbers | head
Chain INPUT (policy ACCEPT)
num target prot opt source destination
1 DROP tcp -- 0.0.0.0/0 0.0.0.0/0 tcp dpt:2375
Що це означає: Ви зупинили витік на рівні хоста (не підміна для виправлення на межі мережі).
Рішення: тримайте це правило, поки не виправите конфіг демона і не перевірите, що зовнішня експозиція зникла.
Завдання 14: Виправити демон, щоб видалити TCP-лістенер (systemd override)
cr0x@server:~$ sudo systemctl edit docker
# Creates /etc/systemd/system/docker.service.d/override.conf
cr0x@server:~$ sudo cat /etc/systemd/system/docker.service.d/override.conf
[Service]
ExecStart=
ExecStart=/usr/bin/dockerd -H fd:// --containerd=/run/containerd/containerd.sock
Що це означає: Ви зробили оверрайд юніта, щоб прибрати аргумент TCP-host.
Рішення: перезавантажте systemd і рестартуйте Docker; потім перевірте сокети і зовнішню доступність.
Завдання 15: Безпечно перезапустити Docker і підтвердити сокети
cr0x@server:~$ sudo systemctl daemon-reload
cr0x@server:~$ sudo systemctl restart docker
cr0x@server:~$ sudo ss -lntp | grep -E ':(2375|2376)\s' || echo "no docker tcp listener"
no docker tcp listener
Що це означає: Демон більше не слухає на TCP.
Рішення: тепер перевірте з зовнішньої мережі. Не довіряйте лише локальним перевіркам.
Завдання 16: Якщо вам дійсно потрібен віддалений доступ — вимагайте взаємний TLS на 2376
Якщо ваша автоматизація потребує цього — гаразд. Але ви все одно не маєте права використовувати plaintext.
У Docker «TLS ввімкнено» нічого не значить, якщо ви не вимагаєте аутентифікацію клієнтськими сертифікатами.
cr0x@server:~$ sudo cat /etc/docker/daemon.json
{
"hosts": ["unix:///var/run/docker.sock", "tcp://0.0.0.0:2376"],
"tlsverify": true,
"tlscacert": "/etc/docker/pki/ca.pem",
"tlscert": "/etc/docker/pki/server-cert.pem",
"tlskey": "/etc/docker/pki/server-key.pem"
}
Що це означає: Демон вимагатиме клієнтський сертифікат, підписаний вашим CA.
Рішення: дозволяйте 2376 лише з мережі управління і розповсюджуйте клієнтські ключі так само, як SSH-ключі: мінімально, з ротацією і логуванням.
Завдання 17: Протестувати TLS з клієнтом, що має сертифікат (має бути успішно)
cr0x@server:~$ docker --host tcp://198.51.100.10:2376 \
--tlsverify \
--tlscacert ./ca.pem \
--tlscert ./client-cert.pem \
--tlskey ./client-key.pem \
version
Client: Docker Engine - Community
Version: 26.1.0
API version: 1.45
Server: Docker Engine - Community
Engine:
Version: 26.1.0
API version: 1.45 (minimum version 1.24)
Що це означає: Ваш клієнт може автентифікуватися і говорити з демоном.
Рішення: продовжуйте лише якщо також доведете, що неавтентифікований доступ не працює.
Завдання 18: Перевірити неавтентифікований доступ до 2376 (має бути відхилено)
cr0x@server:~$ curl -s https://198.51.100.10:2376/version | head
Client sent an HTTP request to an HTTPS server.
Що це означає: Plain HTTP відхилено. Тепер спробуйте HTTPS без клієнтського сертифіката.
Рішення: якщо HTTPS без клієнтського сертифіката працює — ви все ще відкриті.
cr0x@server:~$ curl -sk https://198.51.100.10:2376/version | head
TLS handshake error: remote error: tls: bad certificate
Що це означає: Демон вимагає дійсний клієнтський сертифікат.
Рішення: це мінімальний бар для того, щоб «віддалений Docker API» не був відкритими дверима до root.
Завдання 19: Шукати підозрілу персистентність на хості (systemd)
cr0x@server:~$ systemctl list-unit-files --type=service | grep -E 'docker|container|update|agent' | tail
docker.service enabled
containerd.service enabled
system-update.service disabled
Що це означає: Це легка перевірка. Вона не вловить усе, але часто помічає недбалу персистентність.
Рішення: якщо підозрюєте компрометацію, продовжуйте з перевіркою цілісності файлів і планом перебудови.
Практики загартування, що витримують продакшн
1) Правильний дефолт: ніякого TCP-сокета, лише Unix-сокет
Найчистіший контроль — це відсутність віддаленої панелі керування взагалі. Використовуйте SSH, щоб дістатися до хоста, і звертайтеся до Unix-сокета локально.
Так, це менш «cloud-native». Це також менш привабливо для зловмисників.
Якщо потрібна віддалена оркестрація, подумайте про інструменти, що не вимагають широкого відкриття демона. Багато команд використовують SSH-тунелі
для рідкісних випадків, коли віддалений доступ необхідний тимчасово.
2) Якщо треба виставляти Docker: взаємний TLS, жорстке мережеве обмеження та короткострокові облікові дані
Взаємний TLS — це обов’язково. Не «TLS з серверним сертифікатом». Не «basic auth за проксі». Клієнтські сертифікати, підписані вашим CA,
з ротацією як будь-яких інших облікових даних.
Далі — обмежуйте доступ. «Лише IP офісу» — це не обмеження; це мрія з VPN-крихкістю. Вам потрібно:
- Дозволити вхід лише з виділеного підмережі управління
- Явно заборонити доступ звідусіль
- Переглядайте правила security group як код (бо це поведінка продакшну)
3) Не ставте загальний reverse proxy перед Docker API, якщо не знаєте, що робите
Помістити HTTP API Docker за reverse proxy здається акуратно, поки хтось не додасть дозволений маршрут, не відключить автентифікацію клієнта «для тесту»
або не залогуватиме чутливі заголовки. Також: Docker API не проєктувався як публічний веб-застосунок. Це контроль-плейн.
Якщо наполягаєте на проксі, він має забезпечувати взаємний TLS наскрізь, строгі allowlist-ендпоїнти і логування, корисне для інцидент-реагування
без витоку секретів. Більшість проксі в реальності стають складним способом зробити систему небезпечною.
4) Ставтеся до членства в групі docker як до привілейованого доступу
Тут корпоративна реальність боляче б’є. Люди додають CI-runner-ів, розробників і «тимчасових підрядників» в групу docker, бо так швидше, ніж
думати про коректні межі привілеїв. Це створює внутрішній латеральний шлях, що обходить аудит sudo.
Мета: тримати доступ до демона за автоматизацією з підтвердженнями і логуванням, або за root-shell з MFA.
Все інше — це розподілений root з додатковими кроками.
5) Вбудуйте детекцію в базову конфігурацію
Ви можете запобігати експозиції і все одно хотіти детекцію, бо реальність неоднорідна. Базові перевірки, що ловлять найгірші помилки:
- Алерт, якщо
dockerdприв’язується до0.0.0.0:2375або:::2375 - Алерт на правила фаєрволу, що дозволяють вхід 2375/2376 з неуповноважених CIDR
- Алерт на нові привілейовані контейнери або контейнери, що монтують
/ - Трекати події створення/старту контейнерів централізовано
Три корпоративні історії з практики
Міні-історія 1: Інцидент через помилкове припущення
Середня SaaS-компанія мала стейджинг, що здавався «внутрішнім», бо працював у VPC хмари. Команда вважала, що VPC — значить приватна.
Насправді — ні. Один вузол мав публічну IP для зручності і security group з правилом, що дозволяло вхід на 2375 звідусіль,
бо підряднику потрібно було зробити одноразову міграцію.
Ніхто не видалив правило. Підрядник пішов. Тікет загубився під квартальною реорганізацією. Через місяць вузол почав перегріватися,
провайдер обмежив ресурси. Початковою гіпотезою був «поганий деплой», бо зазвичай так і буває.
Вони відкотили зміни. CPU залишився завантаженим.
Інженер на виклику нарешті виконав docker ps і побачив контейнер з безглуздим ім’ям і нещодавнім часом створення.
Він вбив його. Він повернувся. Він вбив знову. Він повернувся знову. Саме того дня вони дізналися, що демон доступний зовні і зловмисник просто повторював запити «create container».
Ізоляція була швидкою: заблокували 2375 на краю, зупинили Docker, зробили снапшот диска для аналізу. Болючим була ротація облікових даних.
Стейджинг мав доступ до спільних облікових даних реєстру і декількох «тимчасових» API-ключів, які також використовувалися в девелопменті.
Компрометація не перейшла в інші середовища, але оцінка радіусу ураження забрала тиждень.
Помилкове припущення було не «Docker небезпечний». Помилкове припущення — «внутрішня мережа == безпечно за замовчуванням».
У хмарних мережах «внутрішнє» — це політика, яку потрібно постійно підтримувати, а не відчуття.
Міні-історія 2: Оптимізація, що вдарила по руках
Великий enterprise мав флот збіркових серверів. Зборки були повільні, тож інженер вирішив пришвидшити їх, дозволивши збіркам говорити
напряму з віддаленим Docker-демоном по TCP. Ідея: уникнути nested-virtualization і зменшити локальний диск-чурн.
Це спрацювало. Часи збірки покращилися.
Потім з’явилося «невелике спрощення». Замість роботи з TLS-сертифікатами в CI вони тимчасово переключилися на 2375
з планом «закрити пізніше», захищеного лише IP-allowlist-ом. Той allowlist жив у конфігурації фаєрволу, яку вели інша команда.
Запити на зміни йшли дні. Тому вони відкрили трохи більше. І ще трохи. Зрештою «на тиждень» це стало доступним з великого корпоративного діапазону.
Що пішло не так — не те, що випадковий атакер відразу знайшов це (хоча таке трапляється). Проблема — внутрішній латеральний рух.
Скомпрометований ноутбук розробника підключився до VPN корпорації. Звідти він зміг дістатися до демона.
Атакеру не потрібен домен-адмін. Потрібен один доступний демон і можливість запускати контейнери, що можуть змонтувати секрети з кешу збірок.
Негативні наслідки були жорстокі, бо pipeline став точкою агрегації облікових даних: токени реєстру, ключі підписування, проксі-залежності.
Навіть без повного виходу на хост, читання томів робочих просторів дозволило завдати значної шкоди.
Постінцидентне виправлення не було «швидші збірки». Воно було «ізоляція збірок, що не використовує віддалений root API».
Жарт №2: «Ми додамо TLS пізніше» — це безпечно як «я почну робити бекапи завтра» — план, що рідко виживає в реальності.
Міні-історія 3: Нудна, але правильна практика, що врятувала день
Інша компанія мала строгий базлайн: Docker-демони не мали слухати на TCP взагалі.
Якщо команда потребувала віддаленого керування, вони заходили по SSH на басціон і робили короткотривалий тунель до Unix-сокета з записом сесій.
Усі трохи ненавиділи це. Саме так ви розумієте, що практика ймовірно корисна.
Одного дня спрацював моніторинг: з production-ноди різко зросли вихідні з’єднання до незнайомих діапазонів.
Першою підозрою був «відкритий Docker API», бо команда з безпеки вже бачила це раніше.
На виклику перевірили ss -lntp і нічого не побачили на 2375/2376. Це швидко виключило один великий клас помилок.
Вони переключилися. Виявилося, що скомпрометований контейнер додатка робив звернення, а не скомпрометований хост.
Оскільки демон не був доступний віддалено, атакер не міг легко створювати привілейовані контейнери або монтувати файлову систему хоста.
Радіус ураження залишився в межах дозволів і секретів додатка.
Реакція все одно була серйозною: ротувати секрети додатка, патчити вразливу залежність, перевилити деплой.
Але вони уникли «перебудувати весь флот хостів і ротувати все» кошмару. Нудний базлайн не запобіг усім проблемам,
але запобіг перетворенню проблеми на катастрофу.
Поширені помилки: симптом → корінь проблеми → виправлення
1) Симптом: CPU завантажений, дивні контейнери, незрозумілий egress
Корінь проблеми: Docker daemon відкритий на 2375; атакер запускає майнери і відновлює їх після видалення.
Виправлення: негайно блокуйте вхід 2375/2376, видаліть TCP-лістенер з dockerd, перебудуйте хост якщо привілейовані контейнери монтували хост.
2) Симптом: «Ми використовуємо TLS», але будь-хто може підключитися
Корінь проблеми: TLS тільки на сервері; tlsverify не ввімкнено; автентифікація клієнтським сертифікатом не застосована.
Виправлення: встановіть "tlsverify": true і вимагайте клієнтські сертифікати, підписані вашим CA; підтвердіть, що неавтентифікований curl -sk не працює.
3) Симптом: Docker «не слухає», але віддалений доступ все одно є
Корінь проблеми: сайдкар-проксі або інший процес пересилає трафік до Unix-сокета; або інший екземпляр демона прив’язаний через systemd drop-in.
Виправлення: перевірте systemctl cat docker на наявність override-ів, шукайте конфіги проксі і верифікуйте реальні сокети через ss -lntp.
4) Симптом: Розробники можуть «просто запускати Docker-команди» без sudo
Корінь проблеми: користувачі в групі docker, яка фактично дорівнює root на цьому хості.
Виправлення: видаліть непотрібних користувачів з групи; примусьте привілейовані операції проходити через контрольовану автоматизацію або sudo з аудитом.
5) Симптом: Контейнер постійно «повертається» після видалення
Корінь проблеми: зовнішній актор викликає Remote API для відтворення контейнерів; або скомпрометований оркестратор/CI це робить.
Виправлення: спочатку відключіть мережевий доступ до демона, потім розслідуйте облікові дані оркестратора і логи.
6) Симптом: «Ми відкрили лише VPC» і все одно постраждали
Корінь проблеми: VPC не настільки приватна, як ви думаєте: публічна IP, peering, VPN, неправильно маршрутизований security group або скомпрометований внутрішній хост.
Виправлення: вважайте внутрішні мережі ворожими; обмежуйте до підмереж управління, вимагайте взаємний TLS і сегментуйте збіркові системи від загального доступу.
Контрольні списки / покроковий план
Покроково: як захистити хост сьогодні
-
Знайти прослуховування: виконайте
ss -lntpі впевніться, що ніхто не прив’язується до 2375/2376 на публічних інтерфейсах. -
Підтвердити конфіг сервісу: перевірте
systemctl cat dockerі/etc/docker/daemon.jsonна наявністьtcp://host-ів. - Заблокувати на кордоні: видаліть inbound security group / правила фаєрволу для 2375 і 2376, якщо у вас немає мережі управління і взаємного TLS.
- Блокувати локально для defense-in-depth: тимчасово дропайте вхідний 2375 через iptables/nftables (контейнмент і запасний захід).
-
Зменшити розповсюдження привілеїв: проведіть аудит групи
docker; видаліть користувачів, яким це не потрібно. -
Увімкнути моніторинг: ставте алерти на нові привілейовані контейнери, контейнери, що монтують
/, та на будь-який новий TCP-лістенер дляdockerd. - Задокументувати процес винятків: якщо команда дійсно потребує віддаленого Docker, вимагайте взаємного TLS + CIDR-обмежень + строку дії правила фаєрволу.
Покроково: якщо підозрюєте, що експозиція вже сталася
- Ізолювати: негайно блокуйте вхід до Docker API (край + локально). Не сперечайтеся.
- Зберегти докази: експортуйте
journalctl -u docker,docker ps -a,docker inspectпідозрілих контейнерів і системні логи. - Оцінити компрометацію хоста: якщо знайдете привілейовані контейнери з монтуванням хоста — вважайте, що було втручання на рівні хоста.
- Ротація облікових даних: токени реєстру, ролі інстансів у хмарі (якщо застосовні), секрети додатків на диску, CI-облікові дані, використані на цій ноді.
- Перебудувати чисто: віддавайте перевагу перебудові ноди з перевіреного образу замість «очищення». Очищення — це шлях до пропуску персистенції.
- Закрити ланцюг: додайте детекцію, щоб та сама експозиція не повернулася під час наступної «тимчасової» зміни.
Політика, що працює
- Заборонити plaintext Docker TCP (2375) повністю. Без винятків.
- Дозволяти TLS Docker TCP (2376) лише з взаємним TLS і жорстким мережевим обмеженням.
- Ставитися до доступу до демона Docker як до продакшн-root. Бо так воно й є.
Питання та відповіді
1) Чи коли-небудь прийнятно відкривати порт 2375?
Ні. Не «рідко», не «за фаєрволом», не «тільки на тиждень». Plaintext неавтентифікований Docker API — це віддалений root за дизайном.
Якщо потрібен віддалений контроль — використовуйте взаємний TLS на 2376 і жорсткі мережеві обмеження — або краще взагалі не виставляйте.
2) Якщо я прив’яжу Docker на 127.0.0.1, чи я в безпеці?
Безпечніше — так. Повністю безпечно — не обов’язково. Локальні прив’язки зменшують віддалену експозицію, але будь-яка локальна компрометація
(включно з SSRF у веб-додатку, що може звертатися до localhost) все ще може ним зловживати. Ставтеся до цього як до чутливого ресурсу навіть локально.
3) Чому Docker API вважають рівнозначним root?
Тому що він може запускати привілейовані контейнери, монтувати шляхи хоста і маніпулювати мережею. Це дії адміністратора хоста.
Якщо демон може це робити, а API може інструктувати демон — API є адміністративним інтерфейсом хоста.
4) Чи вирішує проблему режим rootless?
Він зменшує радіус ураження компрометації демона, але не робить відкритий API прийнятним.
Атакер все ще може запускати навантаження, красти секрети того користувача і потенційно пивотувати через облікові дані та мережевий доступ.
5) Ми працюємо з Kubernetes; чи це все ще актуально?
Так. Багато Kubernetes-нoдів використовують інші runtime-і, але у вас все ще може бути встановлений Docker для legacy-робочих навантажень,
дебагу або CI. Будь-який відкритий демон на ноді — це плацдарм для атак у середовищі кластера.
6) Чи можна поставити basic auth перед Docker API через reverse proxy?
Можна, але не називайте це «безпечно», якщо у вас немає сильного транспортного захисту, строгого allowlisting кінцевих точок і правдоподібної історії ротації облікових даних.
Взаємний TLS — стандарт, бо його складніше випадково послабити.
7) Який найшвидший спосіб дізнатися, чи ми зараз відкриті?
Ззовні вашої мережі спробуйте отримати /version на 2375. Якщо відповідає — ви відкриті.
Внутрішньо — перевірте ss -lntp і правила фаєрволу/security group для 2375/2376.
8) Якщо ми були відкриті, чи справді потрібно перебудувати хост?
Якщо атакери запускали привілейовані контейнери з монтуванням хоста або ви не можете довести, що цього не було — перебудова є розумним вибором.
«Ми видалили контейнер» — не гарантія. Персистенція дешева.
9) Як зберегти автоматизацію без виставлення Docker?
Працюйте автоматизацію на самому хості (або через SSH), використовуйте басціон з аудиту сесій або перемістіть збіркові процеси в ізольовані билдери, де сокет Docker ніколи не доступний з непроханих мереж.
Якщо віддалений контроль обов’язковий — використовуйте взаємний TLS і підмережі управління.
Висновок: що робити далі
Docker Remote API — це потужний інструмент. Покладіть його на публічну лавку — і хтось збудує сарай, який ви не замовляли.
Ваше завдання — зробити так, щоб «випадково відкритий root» став структурно важким для реалізації.
Практичні наступні кроки:
- Проскануйте ваш флот на прослуховування 2375/2376 і на аргументи
dockerd, що містятьtcp://. - Приберіть plaintext 2375 скрізь. Якщо знайдете — вважайте це інцидентом до перевірки.
- Якщо потрібен віддалений Docker, вимагайте взаємний TLS, обмежуйте доступ CIDR підмереж управління і ротуйте сертифікати регулярно.
- Аудитуйте членство групи
docker, якби це було sudo — бо так воно й є. - Напишіть runbook зараз, поки майнер ще не з’явився.