1. Архитектура Docker vs Виртуализация: фундаментальные отличия и векторы атак
Архитектура Docker vs Виртуализация: фундаментальные отличия и векторы атак
В исходном коде ядра Linux не существует понятия «контейнер». В отличие от виртуальных машин, которые опираются на аппаратные инструкции процессора (Intel VT-x, AMD-V) и чётко выделенные структуры данных гипервизора, контейнер — это абстракция, иллюзия, созданная исключительно для пространства пользователя. Для ядра Linux контейнер с базой данных PostgreSQL или веб-сервером Nginx — это обычный процесс, такой же, как системный демон cron или сессия bash. Понимание этого архитектурного факта — отправная точка для выстраивания любой стратегии безопасности в контейнерных средах.
Специалисты по информационной безопасности часто переносят ментальную модель защиты виртуальных машин на Docker-инфраструктуру. Эта когнитивная ошибка приводит к тому, что контейнеры пытаются защищать периметровыми межсетевыми экранами и классическими антивирусными агентами, игнорируя их истинную природу. Чтобы эффективно защищать Docker, необходимо деконструировать его архитектуру и понять, чем граница безопасности контейнера отличается от границы безопасности виртуальной машины.
Деконструкция иллюзии: как работает контейнер
Когда мы запускаем виртуальную машину (ВМ), гипервизор эмулирует для неё полноценное аппаратное обеспечение: виртуальный процессор, оперативную память, жесткий диск и сетевой адаптер. Поверх этого виртуального железа устанавливается полноценная гостевая операционная система со своим собственным ядром.
Контейнеризация работает на совершенно ином уровне — на уровне операционной системы. Docker Engine не эмулирует оборудование. Он использует функции существующего ядра хостовой ОС (Linux) для создания изолированных сред выполнения.
Эта изоляция строится на двух фундаментальных механизмах ядра (которые будут детально разобраны в следующих главах):
Если на хост-системе выполнить команду ps aux, можно увидеть все процессы всех запущенных контейнеров. Ядро видит их насквозь. Процесс Nginx, который внутри контейнера имеет PID 1, на хостовой машине будет иметь, например, PID 34512.
!Сравнение архитектуры виртуальной машины и контейнера Docker
Архитектурная разница формирует совершенно разные поверхности атаки. В случае виртуальной машины злоумышленник, получивший права root внутри гостевой ОС, всё ещё заперт внутри этой ОС. Чтобы скомпрометировать физический сервер (хост), ему необходимо совершить побег из виртуальной машины (VM Escape), найдя уязвимость в самом гипервизоре (например, в эмуляторе флоппи-дисковода или сетевого адаптера). Гипервизоры имеют очень узкий интерфейс взаимодействия с гостевой ОС (гипервызовы), что делает такие уязвимости редкими и сложными в эксплуатации.
В случае с Docker злоумышленник, получивший права root внутри контейнера, уже находится на хостовой операционной системе. От полного контроля над сервером его отделяет не аппаратный барьер гипервизора, а лишь логические ограничения ядра Linux (Namespaces, cgroups, Capabilities, Seccomp).
Проблема общего ядра (The Shared Kernel Dilemma)
Самое критичное отличие контейнеров от ВМ с точки зрения безопасности — это разделение одного ядра операционной системы между хостом и всеми запущенными на нём контейнерами.
Современные процессоры используют кольца защиты (Protection Rings) для разграничения привилегий. Ядро операционной системы работает в самом привилегированном кольце (Kernel Space). Пользовательские приложения, включая все процессы внутри Docker-контейнеров, работают в непривилегированном кольце (User Space).
Каждый раз, когда процессу в контейнере нужно прочитать файл, отправить сетевой пакет или выделить память, он должен обратиться к ядру через механизм системных вызовов (syscalls). Ядро Linux предоставляет более 300 различных системных вызовов. Это означает, что интерфейс между потенциально скомпрометированным контейнером и критическим ядром хоста состоит из более чем 300 сложных функций, написанных на языке C.
!Симуляция паники ядра: ВМ против Контейнера
Общее ядро порождает два критических риска:
Исторический пример — уязвимость Dirty COW (CVE-2016-5195), связанная с состоянием гонки в подсистеме памяти ядра Linux. Эксплойт для этой уязвимости, запущенный внутри непривилегированного Docker-контейнера, позволял модифицировать файлы на хостовой системе, доступные только для чтения, что приводило к получению прав root на самом хосте. Контейнерная изоляция не могла этому помешать, так как уязвимость находилась в самом ядре Ring_0, которое обрабатывало запросы от контейнера.
Архитектура Docker и новые векторы атак
Помимо рисков, связанных с ядром Linux, сама архитектура Docker вводит новые компоненты, которые становятся целями для атакующих. Классическая установка Docker состоит из клиентской утилиты (Docker CLI) и фонового процесса (Docker Daemon или dockerd).
Docker Daemon — это сердце системы. Он управляет жизненным циклом контейнеров, образами, сетями и томами. Ключевая проблема безопасности заключается в том, что по умолчанию Docker Daemon работает с правами пользователя root.
Взаимодействие между клиентом и демоном происходит через UNIX-сокет, обычно расположенный по пути /var/run/docker.sock. Этот сокет — эквивалент root-доступа к серверу. Любой процесс, имеющий права на запись в этот сокет, может отправить демону команду на создание нового контейнера с максимальными привилегиями, примонтировать корневую файловую систему хоста и полностью захватить сервер.
Исходя из архитектуры, ландшафт угроз для Docker-инфраструктуры можно разделить на четыре макро-вектора.
Вектор 1: Атаки на изоляцию ядра (Kernel Exploits)
Как обсуждалось выше, вектор направлен на эксплуатацию уязвимостей в системных вызовах Linux. Атакующий ищет способы обойти ограничения Namespaces или использует LPE-эксплойты. Защита от этого вектора требует строгой фильтрации системных вызовов (профили Seccomp) и регулярного обновления ядра хост-системы. В высоконадежных средах этот риск минимизируют с помощью sandboxed-контейнеров (например, gVisor или Kata Containers), которые добавляют дополнительный слой изоляции между приложением и ядром хоста, перехватывая и фильтруя системные вызовы.Вектор 2: Эксплуатация архитектуры и API (Daemon & Socket)
Вектор направлен на сам Docker Engine. Самая частая ошибка конфигурации — проброс сокета/var/run/docker.sock внутрь контейнера. Разработчики часто делают это для CI/CD систем (например, Jenkins или GitLab Runner), чтобы контейнер мог сам собирать другие контейнеры (паттерн Docker-in-Docker или Docker-out-of-Docker).
Если злоумышленник компрометирует такой CI/CD контейнер (например, через RCE в веб-интерфейсе Jenkins), он получает доступ к сокету. Отправив HTTP-запрос в сокет, он может создать контейнер с флагом --privileged и примонтированным корнем /, совершив тривиальный побег на хост. Также к этому вектору относится незащищенный TCP-порт Docker API (обычно 2375), выставленный в интернет без TLS-аутентификации.Вектор 3: Небезопасные конфигурации Runtime (Misconfigurations)
По умолчанию Docker предоставляет разумный баланс между удобством и безопасностью, но разработчики часто ослабляют изоляцию для решения локальных проблем. Использование флага--privileged отключает практически все механизмы защиты: контейнер получает доступ ко всем устройствам хоста (/dev), обходит ограничения cgroups и получает полный набор привилегий (Linux Capabilities). Запуск приложения от имени пользователя root внутри контейнера (что происходит по умолчанию, если не указана директива USER в Dockerfile) также значительно упрощает эксплуатацию любых найденных уязвимостей.Вектор 4: Атаки на цепочку поставок (Supply Chain)
В отличие от ВМ, где ОС устанавливается из доверенного ISO-образа, контейнеры собираются из слоистых образов, скачиваемых из публичных реестров (Docker Hub). Вектор включает в себя:ubunto вместо ubuntu).docker history или утилит вроде dive.Сдвиг парадигмы безопасности
Понимание архитектурных отличий требует от специалиста ИБ изменения подхода к защите.
Во-первых, граница безопасности смещается. В мире виртуализации границей был гипервизор и сетевой периметр. В мире контейнеров границей является ядро Linux и его механизмы разграничения доступа. Защита должна фокусироваться на принципе наименьших привилегий (Principle of Least Privilege) на уровне процессов: дроппинг ненужных Capabilities, запрет на получение новых привилегий (no-new-privileges), использование профилей AppArmor/SELinux и ограничение доступных системных вызовов.
Во-вторых, контейнеры эфемерны. Они могут создаваться и уничтожаться сотни раз в день. Классические методы реагирования на инциденты (сохранение дампа памяти, изоляция хоста в карантинной сети) работают плохо. Если контейнер скомпрометирован, злоумышленник может просто остановить его, уничтожив все следы в оперативной памяти и временной файловой системе. Это требует внедрения концепции неизменяемой инфраструктуры (Immutable Infrastructure), где контейнеры работают в режиме Read-Only корневой файловой системы, а все логи немедленно отправляются в централизованное хранилище (SIEM).
В-третьих, безопасность должна смещаться влево (Shift Left). Поскольку контейнер содержит в себе все зависимости приложения, уязвимости в библиотеках становятся частью образа. Сканирование на уязвимости должно происходить не на работающем сервере, а в пайплайне CI/CD до того, как образ попадет в реестр.
Архитектура Docker обеспечивает превосходную плотность развертывания и скорость работы именно за счет отказа от тяжеловесной аппаратной изоляции. Платой за эту эффективность становится расширенная поверхность атаки на уровне ядра и демона. Успешная защита такой инфраструктуры невозможна без глубокого понимания того, как именно ядро Linux создает иллюзию изолированного пространства, и какие системные механизмы удерживают процесс внутри этой иллюзии.