- Несколько динамических контейнеров и портов на задание.
- Порты рандомизируются при каждом запуске.
- Поддерживаются режимы CTFd: индивидуальный (users) и командный (teams). Владение инстансом всегда per-user (контейнеры привязаны к пользователю), а настройка видимости определяет, могут ли тиммейты просматривать и управлять инстансами друг друга.
- Поддерживаются статические (plaintext или regex), динамические и полу-динамические (шаблонные) флаги.
- Переменная окружения
FLAGвсегда прокидывается в контейнеры при старте: в статическом режиме это заданный флаг, в динамическом/полу-динамическом — уникальный флаг для инстанса. - Всё, что связано с контейнером (включая FRP), настраивается декларативно через лейблы Docker Compose.
- Поддерживаются разные типы сообщений (toasts, modals) о статусе контейнеров, а также совместимость со старыми core-based темами и новыми.
Проксируемые контейнеры должны иметь как минимум первые два лейбла из следующих:
owl.proxy=true- показывает CTFd-Owl, что контейнер нужно проксироватьowl.proxy.port=8080- порт внутри контейнера, на который будет идти трафик (например, 8080)owl.label.conntype=nc- тип подключения (http/https/nc/ssh/telnet), показывается как(nc)передip:portв карточке заданияowl.label.comment=My comment.- показывается как(My comment.)на следующей строке послеip:portв карточке заданияowl.ssh.username=ctf- используется только когдаowl.label.conntype=ssh(показывается какssh ctf@ip -p portв карточке задания)owl.ssh.password=secret- пароль для SSH (опционально; игнорируется в UI, если указанowl.ssh.key)owl.ssh.key=id_rsa- имя SSH-ключа (опционально; в UI имеет приоритет над паролем)
Отображение данных для подключения изменено для nc, telnet и ssh.
Чтобы FRP мог проксировать трафик в контейнер, он должен находиться в сети net, где net:
networks:
net:
external:
name: ctfd_frp_containers
Однако, если в задании есть несколько контейнеров (например, service1 и service2), и service1 делает HTTP-запрос к http://service2, то при запуске задания у нескольких участников в одной сети окажется несколько контейнеров с именем service2. Это приведёт к тому, что Docker DNS начнёт отдавать несколько A-записей.
Чтобы предотвратить это, если в вашем задании несколько сервисов и хотя бы один обращается к другому по имени, не помещайте сервис, которому не нужно проксирование, в net — вместо этого создайте сеть с названием CTFD_PRIVATE_NETWORK и поместите его в неё. CTFD_PRIVATE_NETWORK будет заменено плагином на строку формата {prefix}_user{user_id}_{dirname} в процессе настройки контейнеров.
Требуется: CTFd >= v3.7.7
Скрипт установки:
# Установите Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sh get-docker.sh
# Замените <workdir> на нужную вам директорию
cd <workdir>
git clone https://github.com/CTFd/CTFd.git -b 3.7.7 # Рекомендованная версия, но вы можете обновиться.
git clone https://github.com/mscw-infosec/CTFd-owl.git
cp -r CTFd-owl/* CTFd
mkdir -p /home/dockerНе забудьте задать переменные SECRET_KEY, MYSQL_PASSWORD и другие подобные в вашем docker-compose.yml.
Запустите эту команду в корне CTFd для развёртывания платформы:
docker compose up -d| Options | Content |
|---|---|
| Instance visibility | Управление доступом к запущенным инстансам. Варианты: всем членам команды / у каждого свой (только при Teams Mode в CTFd) |
| Instances menu | Плавающая кнопка внизу слева на странице /challenges, показывающая список всех живых инстансов |
| Max Instances Per User | Максимальное число живых инстансов (по разным таскам) на пользователя |
| Max Instances Per Team | максимальное число живых инстансов на всю команду; значение auto означает «размер команды» |
| Options | Content |
|---|---|
| User Notifications Mode | Как показывать пользователю уведомления: Toast (всплывающие сообщения) или Modal (модальные окна). |
| Toast Strategy | Реализация тостов для поддержки разных тем, basicToasts (core-beta), notifyToasts (core), а также manual реализация - bootstrapToasts |
| Options | Content |
|---|---|
| Docker Flag Prefix | Префикс флага |
| Dynamic Flag Length | Длина генерируемой части для динамического флага |
| Semi-Dynamic Flag Length | Длина по умолчанию для $[!gen!] в полу-динамических шаблонах |
| Docker API URL | URL/путь к API (по умолчанию — unix:///var/run/docker.sock) |
| Max Container Count | Максимальное количество контейнеров (по умолчанию — без ограничений) |
| Docker Container Timeout | Максимальное время жизни контейнера (по истечении контейнер будет удалён) |
| Max Renewal Times | Максимальное количество продлений времени жизни контейнера |
| Options | Content |
|---|---|
| Frp Http Domain Suffix | Суффикс домена FRP (нужен для динамического DNS, используется редко, по умолчанию None) |
| FRPS address | Адрес сервера с FRP, показывается участникам в связке ip:port |
| FRPC address | Адрес клиента с FRP, обычно frpc |
| FRPC port | Порт клиента с FRP, обычно 7440 |
| Frp Direct Minimum Port | Минимальный порт (должен быть идентичен минимальному порту frps в docker-compose.yml) |
| Frp Direct Maximum Port | Максимальный порт (должен быть идентичен максимальному порту frps в docker-compose.yml) |
| Frpc config template | Шаблон конфига с данными FRP, на основе его обновляется frpc.toml |
Ниже приведён пример шаблона конфигурации FRP.
Пожалуйста, создайте случайную строку и замените ей значение auth.token. Не забудьте также обновить auth.token в frp/conf/frps.toml и frp/conf/frpc.toml.
serverAddr = "frps"
serverPort = 80
auth.method = "token"
auth.token = "CHANGE_THIS_TOKEN_TO_RANDOM_VALUE"
webServer.addr = "10.1.0.4"
webServer.port = 7400
webServer.user = "admin"
webServer.password = "admin"
transport.tcpMux = false
transport.poolCount = 1- Пример обычной задачи приведён в
CTFd/plugins/ctfd-owl/source/tasks/sanity-task. - Пример задачи с динамическим флагом приведён в
CTFd/plugins/ctfd-owl/source/tY0u_F1nd_M3asks/dynamic-task. - Пример задачи с SSH приведён в
CTFd/plugins/ctfd-owl/source/tasks/ssh-task.
Во всех случаях контейнер получает FLAG автоматически (смотрите FLAG=${FLAG} в compose-файлах примеров).
Полу-динамические флаги задаются как шаблон в обычном флаге CTFd (plaintext), например:
ctf{Y0u_F1nd_M3_$[!gen!]}—$[!gen!]будет заменён на случайную строку длины Semi-Dynamic Flag Length.ctf{Y0u_F1nd_M3_$[!gen:06!]}— сгенерирует 6 символов (перекрывает настройку длины для конкретного плейсхолдера).
Вы можете создать свои задачи на их основе.
В репозитории также добавлена тема pixo (автор — hmrserver, адаптирована michaelsantosti для CTFd v3.7.7 и JustMarfix для CTFd-Owl). Она доступна в папке themes этого репозитория.






