GO CTF 2026: как прошёл финал в Казани

go_lead

25 октября в казанском ИТ-парке имени Башира Рамеева прошёл финал GO CTF Tatarstan. За победу боролись 20 команд — по десять в школьном зачёте и зачёте СПО. Призовой фонд составил 700 тысяч рублей. Финалисты играли в Attack/Defense: защищали собственные сервисы и искали уязвимости в чужих, чтобы добывать флаги.

В школьном зачёте победила команда daywave, второе место заняла Pudge Fun Club, третье — uz83k1. Среди СПО первой стала o1d_bu7_go1d, серебро досталось FR13NDS, бронза — Cit Code.

Участники решали четыре сервиса:

  • ОРАНЖЕРЕЯ — use-after-free, рассинхрон авторизации по фрагменту URL, обратимая рекуррента.
  • АСТРОЛЯБИЯ — шесть логических уязвимостей (коллизии ID, SSRF, symlink и др.).
  • МАЯК — chosen-plaintext на самодельный FEAL-подобный шифр.
  • ДОК — повторный промокод, обход подписи курсора, prefix-match контракта.

Репозиторий — https://github.com/acisoru/GoCTF-2026-AD

О заданиях и ходе игры нам рассказали победитель СПО зачёта Илья TakuyaYagam1 из o1d_bu7_go1d, и представители школьного зачёта: Тимофей GreenHaze из daywave, Николай Kolya080808 из uz83k1 и Алексей codem4ster из [mshp] BPT2019.

Тёмный зал и орбитальная визуализация

«Финал проходил в большом тёмном зале с неоново-фиолетовыми лучами.»

Тимофей считает, что организаторам удалось передать эстетику киберпанка, хотя ИИ-изображений в оформлении, по его мнению, было слишком много. Отдельно он отметил питание: полноценные обед и ужин на CTF ему встретились впервые.

Особенно понравилась визуализация и её орбитальный блок:

«Порой реально создавалось ощущение что ты сидишь где то в штабе во время галактического сражения», — пишет Тимофей.

Также он отметил и мерч:

«Ещё мерч порадовал. Хоть его было не так много, но за футболки с названиями команд респект.»

Площадку и организацию он оценил на 10 из 10. Алексей тоже похвалил сеть и кофебрейк, а у Ильи не было претензий к площадке и работе организаторов.

Старт и первые атаки

Начало игры ушло на осмотр сервисов, распределение работы и настройку инструментов. В o1d_bu7_go1d четверо участников взяли по сервису, а пятый занялся входящим и исходящим трафиком. BPT2019 первые полчаса не могла получить доступ к вулнбоксу — машине с уязвимыми сервисами. Пока подключиться не удавалось, команда обсуждала, кто чем займётся после получения доступа.

Для анализа кода и исправлений использовали в том числе ИИ. По словам Николая, uz83k1 работала с Codex и DeepSeek. Заранее подготовленный ИИ-оркестратор не запустился, поэтому уже во время финала команда написала другой. Его Николай впоследствии назвал одним из решений, которые помогли в игре.

Первые флаги o1d_bu7_go1d получила на «Оранжерее». По словам Ильи, примерно через 15 минут после старта команда забрала first blood: ошибка в проверке доступа позволяла читать чужие данные. Эксплойт проверили на NPC-команде, получили настоящий флаг и добавили атаку в ферму для автоматического запуска по соперникам.

BPT2019 написала первый эксплойт в течение первых 40 минут открытой сети: в бинарном сервисе нашли IDOR — ошибку контроля доступа к чужим объектам. У uz83k1 первая успешная атака тоже была на веб-сервис. Николай не помнит точный тип уязвимости, но отмечает, что именно эта атака принесла больше всего флагов в начале игры. Дальше сбор и сдачу флагов автоматизировали: эксплойт запускался каждый раунд.

Фильтры, шифрование и симлинки

«Маяк» потребовал более сложного разбора:

«На деле это сервис с зашифрованными очередями сообщений, для которых пользователи могли загружать собственные фильтры. Мы предположили, что через такой фильтр можно добраться до чужих данных на сервере и соответственно читать эти журналы, поэтому пришлось на тестовых сообщениях получить способ расшифровки и уже после сплоит отработал на ура,» — o1d_bu7_go1d.

Николаю запомнился сервис «Астролябия» на Rust:

«Проблема была в том, что при неправильной конфигурации, неточных изменениях в коде и т. п. он просто падал и не поднимался, и мы долго не могли понять этот момент. Когда поняли, сервис падать перестал.»

Алексей также выделил сервис «Астролябия»:

«Больше всего мне понравился сервис на Rust. Там было 6 уязвимостей, и последняя была загрузка tar-архивов и симлинки, там текла именно бизнес логика, и всё было взаимосвязано, поэтому он и запомнился.»

После чтения райтапов Тимофей отметил, что в сервисах сочетались простые уязвимости и задачи, над которыми требовалось подумать, в том числе криптография.

Защита: патч тоже нужно проверять

Чекеры следили за штатной работой сервисов; неудачное исправление снижало SLA — долю успешных проверок. Поэтому закрыть найденную уязвимость было только частью работы: после изменений требовалось убедиться, что сервис сохраняет нужное поведение.

Так произошло у o1d_bu7_go1d с «Маяком». Участник добавил обязательную проверку заголовка X-Queue-Key для запроса POST /api/queues/<id>/filter. Чекер этот заголовок не отправлял и получал 403. По словам Ильи, сервис начал проходить проверки только спустя семь с лишним раундов, когда SLA уже было нулевым.

«Паниковать не стали, вдвоем спокойно пошли разбираться», — вспоминает Илья.

Проверку убрали, вернув исходное поведение метода. Остальные участники в это время продолжали атаковать и работать со своими сервисами.

Находить уязвимости помогал входящий трафик. BPT2019 по запросам соперников разбиралась с кражей своих флагов и вносила точечные исправления. Был и эпизод с нагрузкой в 1800 запросов в секунду: по словам Алексея, запросы шли от разных команд, а SLA падал. Решение этой проблемы поручили девопсу.

Команда uz83k1 ближе к концу игры через tshark восстановила чужую атаку, закрыла уязвимости и написала собственный эксплойт. Результат защиты проверили в следующем раунде: свои флаги терять перестали.

Однако успешная проверка патча не гарантировала полной защиты. В «Доке» o1d_bu7_go1d нашла сценарий, в котором атакующий заранее готовил аккаунт, быстро покупал созданный чекером товар и получал флаг из его груза. Выдачу груза ограничили с учётом времени создания аккаунта и заказа. Старый эксплойт перестал работать, штатные проверки проходили. Но примерно раз в 30–50 раундов соперники всё равно уносили один флаг. Этот обход команда до конца не разгадала.

DoS: нагрузка на сервисы и мониторинг

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

Тимофей тоже рассказал об обмене DoS-атаками с соперниками:

«Из забавного во время самой игры — обмены intended-досами с другими командами. На пике нам прилетало по 200 мегабит трафика, что 4/8 машинке с Packmate давалось тяжко, поэтому порой приходилось его отключать, и мы оставались без мониторинга.»

Николай считает проблемой одновременный DoS одной команды несколькими соперниками: под нагрузкой можно потерять доступ к машине и возможность установить патч. По его словам, с этим столкнулась Pudge Fun Club.

Последние раунды и впечатления от финала

Ближе к финишу команды искали ошибки в чужих патчах. У o1d_bu7_go1d основное внимание переключилось на «Астролябию»: первый эксплойт принёс 44 флага, затем соперники исправили уязвимость. Стабильный новый обход найти не удалось. BPT2019 тоже адаптировала атаки под изменившуюся защиту.

Команда uz83k1 одновременно удерживала третье место и пыталась догнать Pudge Fun Club. По словам Николая, разрыв заметно сократился. Увидев шанс побороться за второе место, команда активнее взялась за сложные цепочки уязвимостей, но итоговый порядок не изменился.

В daywave, по словам Тимофея, за всю игру не заметили сбоев сети, чекеров или игровой инфраструктуры. У Ильи осталось необъяснённое поведение «Дока»: после серии успешных раундов сервис мог на один раунд получить check failed, затем снова вернуться в up. Причину — в сервисе или чекере — команда не установила. Ещё одно его пожелание касается структуры исходников: сервис на Rust в одном файле примерно на тысячу строк затруднял разбор.

Сам финал Илья оценил как «обычный хороший A/D». Тимофей назвал GO CTF одним из лучших посещённых событий сезона — с оговоркой, что сезон только начинается.