Зачастую сервисы на 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 соответственно.
В итоге, в конфигурацию сервиса добавляются примерно следующие строчки: