Уязвимости Docker по умолчанию: миф о безопасности

«По умолчанию Docker — удобный, но абсолютно сырой инструмент. Он даёт вам коробку, но оставляет думать, что она прочнее, чем есть. По умолчанию контейнер доверяет всему, что внутри и снаружи. Безопасность там, где вы её не строите, — отсутствует.»

Какой образ загружается под капотом

Когда вы запускаете docker run nginx, вы подписываетесь на доверие. Что именно? Последнюю версию тега latest с Docker Hub. Этот тег подвижен, его содержимое меняется со временем, и вчерашняя «стабильная» сборка может сегодня включать экспериментальные модули или зависимости с известными уязвимостями, ещё не попавшими в CVE-базы. Система тегов не гарантирует неизменность.

Кто собирал этот образ? Вы не знаете. Dockerfile, использованный для сборки, мог содержать команды вроде RUN apt-get update && apt-get install -y some-package, которая тянет десятки зависимостей, включая ненужные демоны или отладочные утилиты. Многие базовые образы, особенно ubuntu:latest или debian:buster, по умолчанию содержат пакетный менеджер, средства компиляции и даже shell-утилиты, которые в рабочем контейнере избыточны и расширяют поверхность атаки.

Привилегии внутри контейнера

По умолчанию процесс в контейнере запускается от имени root-пользователя (UID 0). Это даёт ему полный контроль над файловой системой контейнера. Если злоумышленнику удастся скомпрометировать приложение внутри (например, через уязвимость в веб-сервере), он получит root-привилегии в изолированном, но всё же опасном пространстве.

Контейнер не является виртуальной машиной, он использует общее с хостом ядро Linux. Уязвимость в ядре, позволяющая сбежать из контейнера (escape), сразу даёт атакующему root на хосте. Запуск от root внутри контейнера сокращает путь для такой атаки. Многие забывают, что даже если в Dockerfile указать USER nobody, базовый образ должен быть подготовлен: иметь созданного пользователя и корректно установленные права на файлы, иначе приложение просто не запустится.

Малоизвестный вектор: capabilities

Помимо проверки UID, ядро Linux управляет правами через механизм capabilities — набор из десятков отдельных привилегий (например, CAP_NET_ADMIN для управления сетью, CAP_SYS_ADMIN для администрирования системы). По умолчанию Docker запускает контейнер с сокращённым, но всё ещё значительным набором capabilities. Например, сохраняется CAP_NET_RAW, позволяющая работать с raw-сокетами, что может быть использовано для сетевых атак.

Полный список привилегий, которые остаются, неочевиден. Без явного указания флага --cap-drop=ALL и последующего добавления только необходимых (--cap-add=) контейнер сохраняет больше власти, чем требуется для большинства задач.

Сетевая изоляция и проброс портов

При создании контейнера Docker по умолчанию подключает его к сети bridge (docker0). Все контейнеры в одной сети видят друг друга по именам или IP-адресам. Если не настроены правила iptables/nftables отдельно, запущенный контейнер с сервисом на порту 5432 (PostgreSQL) может быть доступен не только для связанных с ним приложений, но и для любого другого контейнера в той же сети, который сможет подобрать или найти учётные данные.

Проброс портов на хост с помощью -p 80:80 открывает сервис не только локально. В большинстве дистрибутивов правила Docker по умолчанию разрешают входящие подключения с любого интерфейса, что может неожиданно выставить внутренний сервис в интернет, если хост имеет публичный IP.

Тома и проброс файлов хоста

Одна из самых опасных опций по умолчанию, это возможность смонтировать любой каталог с хоста внутрь контейнера. Команда docker run -v /home/user:/app/data ... кажется безобидной, но если скомпрометированное приложение в контейнере работает от root, оно получает возможность читать и писать в смонтированную директорию хоста без ограничений. Это прямой путь к утечке данных или подмене системных файлов.

Менее очевидный риск — монтирование Docker-сокета (-v /var/run/docker.sock:/var/run/docker.sock). Эта практика иногда используется для управления контейнерами изнутри других контейнеров (CI/CD). Но если доступ к сокету получит злоумышленник, он сможет выполнять любые Docker-команды от имени хоста, по сути получая контроль над всей средой.

Неявные зависимости и secrets

Контейнеры часто получают конфигурацию через переменные окружения (--env DB_PASSWORD=secret). Эти переменные видны любому процессу внутри контейнера и могут быть прочитаны через /proc/self/environ или выведены ошибкой в логах. По умолчанию Docker не шифрует и не маскирует эти данные.

Более того, многие образы включают в себя конфигурационные файлы с дефолтными паролями или ключами, оставшимися от этапа сборки. Проверка на наличие файлов вроде .env, config.json или id_rsa внутри образа перед его использованием — не стандартная процедура, а ответственность пользователя.

Что можно сделать сразу

Безопасность не добавляется одной командой, это набор практик. Вот с чего стоит начать, чтобы уйти от уязвимых настроек по умолчанию:

  • Используйте конкретные теги образов, а не latest. Например, nginx:1.25-alpine. Альпийские (Alpine) образы часто меньше и содержат меньше пакетов.
  • Запускайте приложение от непривилегированного пользователя. Создавайте его в Dockerfile (RUN adduser -D myuser) и переключайтесь с USER myuser.
  • Ограничивайте capabilities: docker run --cap-drop=ALL --cap-add=NET_BIND_SERVICE .... Определите минимальный набор для вашего приложения.
  • Изолируйте сеть. Создавайте отдельные пользовательские сети Docker для групп сервисов и контролируйте доступ между ними.
  • Никогда не монтируйте Docker socket в контейнеры, не предназначенные для оркестрации. Для данных используйте named volumes вместо монтирования произвольных путей хоста.
  • Для передачи секретов используйте специальные механизмы Docker Swarm или Kubernetes Secrets, либо монтируйте файлы с правами только на чтение.
  • Регулярно сканируйте образы на уязвимости с помощью инструментов вроде Trivy или Docker Scout, даже для базовых образов.

Почему безопасность не стоит по умолчанию

Docker создавался как инструмент для разработчиков, где на первом месте была простота и скорость развёртывания. Жёсткие ограничения безопасности усложнили бы начальное знакомство. Парадокс в том, что контейнер стал промышленным стандартом, а менталитет использования остался «для разработки».

Безопасная конфигурация требует понимания не только Docker, но и механизмов Linux (namespaces, cgroups, capabilities, seccomp). Это высокий порог входа. Поэтому по умолчанию выбраны настройки, которые гарантированно работают для большинства приложений, ценой избыточных привилегий. Задача сместилась с «запустить» на «запустить безопасно», и это уже ответственность того, кто запускает.

Оставьте комментарий