base

Docker: как, зачем и почему

2020-10-19 03:46
Зачастую сервисы на Attack-Defense CTF поднимаются в контейнерах, чтобы как можно больше обезопасить основную систему от уязвимостей в тасках. Чаще всего из контейнеров встречается Docker, так как его легко настроить и запустить в рамках нескольких сервисов. Подробнее прочитать про устройство Docker и основные команды можно, например, здесь

Из докер-контейнера можно “пробросить” порты на основную систему, то есть весь трафик на порт системы, на которой запущен демон докера, пойдет на соответствующий порт докер-контейнера.

Для более удобной работы с несколькими связанными сервисами (например, веб-приложение таска и его база данных) в связке с Docker используется docker-compose. Это утилита, которая умеет запускать несколько докер-контейнеров внутри одной “сети” (внутри сети контейнеры могут обращаться друг другу по именам, пример будет рассмотрен позднее), а также в удобном формате (YAML) описывать, всю конфигурацию сервиса. 

Рассмотрим пример сервиса, использующего docker-compose: https://github.com/C4T-BuT-S4D/training-15-09-19/tree/master/services/alikekspress  (здесь и далее примеры берутся с тренировок C4T BuT S4D). Интересные нам файлы — Dockerfile и docker-compose.yml.

В Dockerfile описана “сборка образа” — какие команды запускаются в базовом публичном образе python:3.7-alpine для его подготовки к запуску сервиса (добавляется исходный код сервиса внутрь образа, устанавливаются зависимости). 
В docker-compose.yml описано, из каких сервисов состоит таск. В данном случае их 2: веб-приложение web и база данных mysql

Для веб-приложения указана папка, в которой лежит Dockerfile в опции build, указано, что порт 7000 основной системы нужно пробросить на порт 8000 контейнера web, а также, что нужно перезапускать контейнер, если он был остановлен не пользователем. 
Для базы данных указан сразу публичный образ (Dockerfile не нужен, так как образ базы данных уже готов к использованию), несколько переменных окружения (образ берет из них параметры инициализации базы данных, такие как пользователь и его пароль), снова проброшен порт (3306) и указано, что папка /var/lib/mysql должна храниться в томе db (объявлен в конце файла). Это сделано для того, чтобы данные, хранящиеся в базе, не терялись при перезапуске сервиса. 

Здесь сразу же видно первую уязвимость — дефолтный пароль от базы данных и проброшенный на неё порт: кто угодно может подключиться, ввести пароль и получить доступ к содержимому (среди которого, как можно догадаться, есть флаги). Посмотрев код сервиса web, можно заметить, что к базе данных сервис подключается по имени сервиса, то есть проброшенный на основную систему порт можно просто убрать, закрыв уязвимость. 

После изменений в таске, чтобы поменялся и запущенный проект, нужно пересобрать его. Для этого его нужно остановить командой docker-compose down (с опцией -v для удаления содержимого томов), а затем снова запустить, собрав образы. Это делается командой docker-compose up --build (опция -d для демонизации, то есть, чтобы “отключиться” от логов контейнеров).

Для просмотра логов можно воспользоваться командой docker-compose logs [--tail N] [-f].
--tail показывает последние N строк вывода, -f “подключается” к выводу и показывает новые логи в онлайне. 

Теперь немного о безопасности. Несмотря на то, что приложения запускаются в контейнерах, они всё ещё могут сильно повлиять на основную систему. 

Так, например, любой процесс внутри контейнера является также процессом на основной системе, поэтому форкбомба, запущенная внутри контейнера, убьет и основную систему. Для защиты от запуска огромного числа процессов внутри контейнера используется опция pids_limit, задающая максимальное число процессов в контейнере. Обязательно к использованию на крупных соревнованиях, в которых запускается socat с fork. 

Также полезно ограничивать, какая доля ресурсов процессора и сколько оперативной памяти доступно контейнерам (если в таске используется, к примеру, apache). Это делается опциями cpus и mem_limit соответственно. 

В итоге, в конфигурацию сервиса добавляются примерно следующие строчки: 

pids_limit: 100
mem_limit: 512M
cpus: 0.25
Howto