Статья Как создать CTF таск: структура флага, сторителлинг и ошибки авторов

Рабочее место CTF-разработчика ночью: три монитора с терминалом, Git-репозиторием и YAML-файлами заданий. Тёплый свет лампы на клавиатуре, холодное свечение экранов в тёмной комнате.


На своём третьем ивенте я убил две недели на web-таск с цепочкой из SQL injection, bypass фильтрации и чтения файлов через LFI. Красивая многоходовка. Через 40 минут после старта первая команда сдала флаг. Радость длилась секунд десять - пока не открыл логи. Никакой injection, никакого bypass. Ребята нашли .git-директорию на веб-сервере, стянули исходники через git dump и прочитали флаг прямо из конфигурационного файла. Две недели работы на intended path, ноль минут на проверку unintended - и результат, за который до сих пор стыдно. Эта статья про правила создания CTF задания, которые помогут не наступить на те же грабли.

Образовательная цель - основа хорошего CTF задания​

Прежде чем открывать редактор кода, ответьте на один вопрос: какой конкретный навык отработает участник, решив этот таск? Не «повеселится» и не «почувствует себя хакером» - а чему научится. Задание без образовательной цели - просто головоломка. Задание с целью - мини-курс по конкретной технике атаки или защиты.

Закономерность, которую подтвердит любой организатор со стажем: задания с ограниченной реальной применимостью быстро приедаются. Стего-задачи, где раз за разом используется один инструмент с разными паролями, или бесконечные Twitter-задания на Base64/ROT13 - сойдут как icebreaker, но при злоупотреблении начинают портить впечатление от всего ивента (источник: hatsoffsecurity.com, гайд по созданию CTF).

Как сформулировать цель​

Три вопроса к каждому таску перед началом разработки:
  1. Какая реальная атака или защитная техника стоит за заданием? SQL injection, buffer overflow, memory forensics - задание должно моделировать реальный сценарий.
  2. Какой инструмент или методология отрабатывается? Если участник после решения не узнал ничего нового о Ghidra, pwntools или Wireshark - задание пустое.
  3. К какой категории (web, crypto, reversing, forensics, pwn) относится таск и какой уровень сложности адекватен для целевой аудитории?
Пример плохо сформулированной цели: «Участник должен найти флаг». Пример хорошей: «Участник изучает Common Modulus Attack на RSA#Attacks_against_plain_RSA) - ситуацию, когда два шифротекста зашифрованы с одинаковым модулем, но разными экспонентами e1 и e2, и применяет расширенный алгоритм Евклида для восстановления открытого текста».

Контекст реального мира особенно критичен для forensics-категории. Если задание построено целиком на артефактах Linux-десктопа - задайте себе вопрос: как часто вы расследуете инциденты на рабочих станциях с Linux? Контекст «скомпрометированный веб-сервер на Linux» или «дамп памяти Windows-машины с подозрительной активностью» - правдоподобнее и учит навыкам, которые пригодятся на реальном IR.

Целевая аудитория определяет сложность

Если аудитория - школьники на первом CTF, easy-таск может быть тривиальным: «Какой командой в Linux посмотреть список файлов?» Для пентестеров с опытом easy должен быть техническим, но с одним шагом решения.

Распределение сложности зависит от контекста. Для обучающего CTF рабочая пропорция - примерно 35% easy, 35% medium, 25% hard и 5% extreme. Для конференции с опытной аудиторией пропорции сдвигаются: 10/25/40/25. Числа - не догма, а отправная точка, подтверждённая практикой организаторов (источник: hatsoffsecurity.com).

УровеньОбучающий CTFКонференция (опытная аудитория)
Easy35%10%
Medium35%25%
Hard25%40%
Extreme5%25%

Будьте честны с собой: написать задание уровня extreme, которое будет одновременно сложным, интересным и решаемым - тяжело даже для опытного автора. Не уверены - лучше сделайте ещё один medium, чем сломанный extreme.

Структура флага CTF: формат, генерация, защита от читерства​

Флаг - строка, которую участник вводит на платформе как ответ. Мелочь на первый взгляд, но неудачный формат способен испортить впечатление от всего задания.

Формат флага и антипаттерны​

Стандартная конвенция: PREFIX{содержимое}. Префикс уникален для каждого соревнования - ugra_, flag{, CTF{, codeby{. Это не просто традиция: увидев знакомый формат в дампе памяти или в бинарнике, участник понимает, что на правильном пути.

Что убивает UX флага:
  • Отсутствие формата. Просто строка s3cur1ty_m4st3r без обёртки. Участник не уверен - это ответ или случайный текст из задания.
  • Неоднозначные символы. O vs 0, l vs 1, I vs l. На одном из ивентов участник потратил 20 минут, перебирая варианты написания - это не challenge design, это UX-провал.
  • Пересечение с обычными данными. Если префикс flag, строка flag может встретиться в документации или исходном коде, создавая ложные срабатывания.
Рабочее правило: используйте hex-строки или однозначные alnum-последовательности как содержимое флага. codeby{a3f8b21c4d} - однозначно. codeby{S3cur1ty_M4st3r!} - нет.

Статические и динамические флаги​

Статический флаг одинаков для всех команд. Проблема очевидна: одна команда решила, скинула ответ в чат - все сдают без решения. Для внутреннего обучающего CTF это терпимо. Для соревновательного - разрушительно.

Динамический флаг генерируется индивидуально для каждой команды. Базовая схема: team_id + challenge_id + секретный ключ, SHA256, первые 16 символов хеша:
Python:
import hashlib, os

def generate_flag(team_id: str, challenge_id: str) -> str:
    secret = os.environ["FLAG_SECRET"]
    seed = f"{team_id}:{challenge_id}:{secret}"
    h = hashlib.sha256(seed.encode()).hexdigest()[:16]
    return f"codeby{{{h}}}"
CTFd поддерживает динамические флаги через плагины: создаётся кастомный challenge type, который генерирует уникальный ответ при обращении команды к заданию. rCTF и другие платформы предлагают аналогичные механизмы. Шаринг ответов отпадает, каждое решение верифицируемо.

Защита от гугления​

Перед деплоем прогоните каждый флаг через поисковик. Находится - меняйте. Это касается не только самого флага, но и промежуточных данных: ключей шифрования, паролей пользователей в задании, названий таблиц в БД. Проверяйте также свои публичные репозитории, Pastebin, gist-ы - всё, куда могли случайно утечь тестовые данные.

CTF сторителлинг: как написать задачу для CTF с историей​

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

Тема соревнования​

Успешные CTF часто объединены общей темой: кинофраншиза, ретро-видеоигры, корпоративный шпионаж, вселенная конкретного сериала. Тема создаёт единое пространство, в котором каждое задание - эпизод. Участник не просто «ищет SQL injection» - он проникает в систему заказов антагониста, и это создаёт мотивацию копать глубже.

Тематизация превращает каждый таск в маленькую историю с целью (источник: contrastsecurity.com, гайд по организации CTF). А ещё тема даёт автору естественный способ встраивать подсказки - через элементы сюжета, а не через прямые указания.

Популярные темы: классические фильмы (Jurassic Park, Back to the Future), ретро-игры, вестерн, корпоративный детектив. Главное - чтобы тема поддерживала, а не замещала техническую суть.

Подсказки в названии и описании​

Хороший CTF challenge design предполагает вшитые наводки. Пример: задание называется «Financial PATH», в описании - история сотрудника, чей «карьерный путь» привёл его к финансовым секретам компании. Слово PATH - подсказка на path traversal. Для новичка - незаметно. Для внимательного участника - вектор атаки.

Формализованная система подсказок с штрафами за использование работает на ивентах с разношёрстной аудиторией:
  • Бесплатная подсказка: общее направление («Посмотри на параметры запроса»)
  • Подсказка за -50 очков: конкретнее («Попробуй UNION SELECT»)
  • Подсказка за -100 очков: почти ответ («Таблица называется admin_secrets»)
Команды разного уровня доводят задание до конца, соревновательность для сильных участников не страдает.

Пасхалки и writeup как часть нарратива​

Дополнительные данные, не связанные с решением, создают атмосферу: переписка сотрудников в forensics-образе, шуточные комментарии в исходном коде, ложные следы с забавными сообщениями. По моему опыту, пасхалки становятся темой для обсуждения после ивента и формируют community-эффект.

Побочный эффект: дополнительные данные повышают сложность. Если в forensics-образе три docx-файла и вопрос «какой файл открывался в эту дату» - ответ подбирается перебором. Если файлов тридцать - придётся искать артефакты правильным способом. Команда SANS DFIR, к примеру, вкладывает серьёзные усилия в генерацию реалистичных данных для своих capstone-ивентов - и потом переиспользует эти образы годами, каждый раз обнаруживая новые артефакты.

Авторский CTF task writeup - такая же часть сторителлинга, как само задание. Writeup фиксирует intended solution, объясняет ход мысли автора и закрепляет образовательную цель. Правило простое: если writeup не написан до деплоя - задание не готово.

Тестирование CTF заданий: охота на unintended solution​

Тестирование - самая недооценённая фаза разработки CTF соревнования. Большинство провалов связаны не с плохой идеей, а с тем, что автор не пытался сломать собственное задание.

Что такое unintended solution и как их искать​

Unintended solution - путь к флагу, который автор не предусмотрел. Классика из реальных ивентов:
  • Открытый порт MySQL в Docker-контейнере: участники подключаются к базе напрямую вместо SQL injection через веб-интерфейс
  • .git-директория на веб-сервере: исходники утекают вместе с флагом в конфиге (привет из моего intro)
  • Флаг, видимый через strings binary без полноценного reversing в Ghidra или IDA
  • Файл flag.txt с правами 644 - readable для всех
  • Отсутствие .dockerignore: в контейнер попадают .env, Makefile, тестовые скрипты с ответами
Методика, которая работает: после написания задания отложите его на сутки. Затем попробуйте решить сами, не заглядывая в исходники. Используйте только инструменты, доступные участникам. Забудьте intended path - попробуйте сломать задание нестандартно. Запустите nmap по контейнеру, проверьте стандартные пути (/robots.txt, /.env, /.git/HEAD), натравите strings на каждый бинарник.

Peer review: чужие глаза находят ваши слепые зоны​

Дайте задание коллеге. Объясните только категорию и уровень сложности - больше ничего. Наблюдайте:
  • Решили за 5 минут задание уровня hard - сложность завышена в вашей голове
  • Застряли на easy больше часа - описание задания неудачно, не хватает контекста
  • Нашли unintended path - вы только что спасли свой ивент
Рекомендация от организаторов крупных CTF: каждый автор пишет solution к своему заданию и коммитит его в общий репозиторий. Не каждый автор будет доступен во время соревнования, а другие организаторы должны уметь отвечать на вопросы участников. Письменный solution - обязательное требование, не опция.

Требования к окружению для тестирования​

  • ОС: та же, что на боевом сервере (как правило, Debian/Ubuntu последней LTS)
  • Docker: docker-compose с теми же ограничениями (CPU ≤0.5, RAM ≤256M)
  • Сеть: сканирование открытых портов извне. Всё, что не является частью задания, должно быть закрыто
  • RAM: для локального тестирования хватит 4 ГБ; если поднимаете несколько контейнеров одновременно - 8 ГБ

Ошибки авторов CTF: антипаттерны из реальных ивентов​

За несколько лет организации и авторства тасков накопилась коллекция ошибок - своих и чужих. Делюсь, чтобы вы не повторяли.

Guesswork-задания​

Задание, которое невозможно решить логически. Примеры, подтверждённые жалобами участников на реальных ивентах (источник: contrastsecurity.com): открыть бинарник в конкретном инструменте reversing, прокрутить граф вызовов до определённого масштаба и прочитать флаг из формы графа. Или: получить GPS-координаты, перейти на street view, прокручивать панораму до вывески, взять название и прогнать через MD5.

Правило: если решение зависит от угадывания инструмента или от действий, не выводимых из условия - это плохое задание. У каждого шага должна быть логическая причина.

Незакрытая инфраструктура​

Открытые порты, дефолтные пароли, отсутствие изоляции. Docker-контейнер с портом MySQL наружу и паролем root - превращает четырёхчасовую цепочку в пятиминутное подключение к базе. Минимальный Dockerfile для web-задания:
Код:
FROM python:3.11-slim
RUN useradd -m -s /bin/bash ctfuser
COPY --chown=ctfuser:ctfuser . /app/
WORKDIR /app
USER ctfuser
EXPOSE 8080
CMD ["gunicorn", "--bind", "0.0.0.0:8080", "--workers", "2", "app:app"]
Непривилегированный пользователь, один открытый порт, никаких лишних сервисов. Ограничения CPU и RAM задаются в docker-compose.yml через deploy.resources.limits. Отдельно проверьте, что docker-compose.yml не пробрасывает порт базы данных на хост - это самая частая причина unintended на web-тасках.

Стеганография ради стеганографии​

Стего-задания, где единственная разница - пароль к steghide, утомляют после второго таска. Один-два стего на весь CTF - нормально. Половина заданий в формате «откройте Stegsolve и перебирайте каналы» - ленивый challenge design.

Отсутствие мониторинга​

Если контейнер упал или задание стало нерешаемым из-за действий участника - без мониторинга узнаете об этом от разъярённых команд в чате. Минимум: health-check контейнеров, логирование попыток сдачи флагов, алерт при нетипичной нагрузке или падении сервиса.

Некалиброванная сложность​

«Я решил за 10 минут, значит это easy» - ловушка, в которую попадает каждый начинающий автор. Вы знаете ответ, вы писали код, вы помните каждую строчку. Для объективной оценки сложности - только peer review от человека, который не видел исходников. Других способов нет.

Чеклист автора CTF задания: от драфта до деплоя​

Этот чеклист - концентрат граблей, на которые уже наступили до вас. Проверяйте каждый пункт перед выпуском задания.
  1. Образовательная цель сформулирована: какой навык отрабатывается, какая реальная атака моделируется
  2. Категория и сложность определены, укладываются в общее распределение ивента
  3. Флаг в стандартном формате с однозначными символами, не гуглится, не совпадает с тестовыми данными
  4. Описание содержит достаточно контекста для старта, подсказки вшиты в текст
  5. Docker-контейнер: непривилегированный пользователь, единственный нужный порт открыт, CPU и RAM ограничены
  6. Unintended paths проверены: .git, .env, robots.txt не торчат наружу, прямой доступ к БД закрыт
  7. Peer review пройден: коллега решил задание без подсказок от вас
  8. Авторский writeup написан до деплоя
  9. Rate limiting настроен для предотвращения брутфорса
  10. Мониторинг развёрнут: health-check, логирование сабмитов, алерт при падении
Если хотя бы один пункт не закрыт - задание не идёт в продакшен. Для обучающего ивента на 20 человек можно ослабить требования к мониторингу. Для CTF на конференции с сотнями участников - добавьте нагрузочное тестирование и план отката.

Большинство авторов, которых я знаю, зацикливаются на техническом совершенстве задания и забывают про эмпатию к решающему. Самые запоминающиеся таски, которые я видел за несколько лет - не самые сложные. Это задания, где автор думал не «как бы мне показать свою крутизну», а «что участник почувствует на каждом шаге». Момент, когда SQL injection проходит и данные начинают вываливаться из базы - это кайф. Момент, когда ты два часа перебираешь инструменты стеганографии без единой зацепки - это не хардкор, это плохой дизайн.

CTF-community страдает от элитизма в создании заданий: «если никто не решил - значит задание крутое». Нет. Если никто не решил - автор не справился. Extreme-таск, который решают 5% команд за 6 часов - отлично. Extreme-таск, который решает только сам автор - провал. В ближайшие пару лет, думаю, мы увидим сдвиг в сторону нарративных CTF с реалистичными сценариями и отход от абстрактных puzzle-тасков. Уже сейчас лучшие ивенты на CTFtime строятся вокруг историй, а не вокруг отдельных уязвимостей. Если хочешь не просто writeup, а пройти всю атаку самому - на WAPT эту цепочку проходят в течение двух модулей с лабами.
 

Вложения

  • 1782987305433.webp
    1782987305433.webp
    27,1 КБ · Просмотры: 11
  • 1782987349652.webp
    1782987349652.webp
    86,4 КБ · Просмотры: 7
  • 1782987402790.webp
    1782987402790.webp
    13,6 КБ · Просмотры: 7
Последнее редактирование:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🧭 Навигатор · ИБ 2026
Не знаешь, какой трек твой?
5 направлений ИБ, реальные зарплаты и точка входа для каждого — в одном треде.
JuniorSenior+
100K → 600K+ ₽ /мес
Открыть навигатор →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab