Постмортем MIPT CTF Quals 2026

Постмортем MIPT CTF Quals 2026

Andrey Ryzhov | @notdenied
Дисклеймер: это сочинение "о том как я провел лето (зачеркнуто) CTF" написано от имени @notdenied — и не претендует на объективность. Хочется поделиться своими мыслями по поводу прошедшего и сделать какую-то работу над ошибками. Если вам интересно, как ломали борду или что привело к ночному простою — можно скипнуть все эти пространные рассуждения без потери смысла.

Что это было? Как мы докатились до этого?

Даже и не знаю, с чего начать. 

В общем и целом, CTF прошёл гладко (и почти всем понравился), не считая простоя с ~4 до 8 утра 15 марта. 

Хоть и всё было организовано на коленке за пачку сухариков, которую я сам себе же купил (борда хостилась у меня дома, а докер для тасок — в одной машинке 32 CPU / 64 RAM за грант в Я.Облаке), кажется, таски были прикольные (пывн уж точно был вишенкой), а борда лежала меньше, чем на кубке CTF (это успех?).

Но обо всём по порядку.

Сама идея провести CTF у нас была сравнительно давно: пока осенью мы с Алексеем @s41nt0l3xus пытались сделать какое-то CTF-комьюнити на Физтехе, мы периодически вкидывали шутки вроде "а вот это могла бы быть таска на MIPT CTF" (ну а какое ещё могло быть название, если мы физтехи?).

Шутки шутками, а в начале года я несколько раз пересекался с @tblackcat и рассказывал про эту идею, и вот благодаря тому, что Арслан в контакте с админкой ФПМИ, мы решили, что можно попробовать вкинуть им сию затею. В сущности, нам не то чтобы много нужно: наверное, только площадка на финалы и немного официальности?

Наученные негативным опытом взаимодействия с различной бюрократией на Физтехе, мы решили, что согласимся на что-то только в случае, если у нас не будет никаких обязательств. Внезапно оказалось, что такой автономный формат всех устраивает — и вот 10 февраля я создал чатик с теми, кто был готов как-то в этом поучаствовать.

важный (бумажный)

Мы сравнительно быстро прикинули, когда хотим провести наше непотребство (на момент выбора даты квалов в выходные 14-15 марта было меньше всего всяких стфок с рейтингом 100 на ctftime; забавно, но впоследствии они откуда-то повылезали, и тут мы немного проиграли), и я начал думать про таски.

У нас не было строгих дедлайнов (я начал пытаться ставить какие-то дедлайны уже в марте, но это не помогло), а потому таски были сделаны во многом по принципу "физтехи две недели думали и ныли, а потом сделали таски за два часа" — то есть в последний момент. Кто-то сделал раньше, кто-то позже — но большая часть работы по непосредственной реализации тасок была сделана в последние неделю-две, а то и за последний день (можно посмотреть по коммитам в репе).

Это, конечно, не могло не выстрелить:

  • Мы не успели потестить таски между собой, а в некоторых случаях — даже не дотестили сами...
  • В частности, лично я в последний момент решил заблекбоксить два веба — и в обоих случаях это сломало таску. Фиксить же их во время работы над инфой оказалось очень некогда.
  • В трёх тасках нашли анинтендеды: флаг в env в photo_storage, тайминг-атака на knowledge_base и возможность забрать флаг напрямую ботом в board_of_secrets. Про последний я узнал во время CTF и хотя бы выкатил revenge. Но флаг в env, конечно, можно было заметить и заранее...
  • Всплыли неожиданные проблемы с бордой: на 30 минут до начала я начал переливать таски с тестовой борды на продовую и узнал (офигел), что на одной из промежуточных проксей есть лимиты на размер тела запроса; в результате я был вынужден побежать переливать все вложения больше пары мегабайт на Я.Диск, и чудом успел сделать это до начала CTF.
  • По схожей причине сначала не был выкачен веб: инстансы почему-то не поднимались на проде, а одну тестовую таску на веб с борды я не мог ни отредачить, ни удалить. Причина — в базе данных для плагина инстансера что-то пошло не по плану, но узнал я это тоже только за 30 минут до начала. К счастью, проблемы удалось решить сравнительно быстро (путем ручного выпила каких-то кишков плагина в одной из его таблиц), и минут через 20 на борде появился и веб (хоть и не весь). Про плагин я подробно напишу далее.

Так, чуть не сорвавшись, всё-таки начался этот ваш MIPT CTF. Что было дальше:

Борда прилегла в первые минуты. Но не от нагрузки на сервер (CPU / RAM), а (вероятно) из-за исчерпания коннектов к БД. В целом, несмотря на селфхост борды на домашнем недосервере, в ресурсы машинки она за всю стфку не уперлась (и в среднем использовала ~20%). К счастью, я был к этому готов, и написал бота для выдачи тасок, а 18 из 20 задач не требовали инстансера. Да и борда поднялась через пару минут, даже до того, как я увеличил лимит подключений.

[оффтоп] Не уходя далеко, стоит сказать, что CTFd — имхо, очень кривая поделка. Да, тут есть и мой skill issue — можно было заранее покрутить лимиты коннектов, потестить нагрузку, подумать про скейлинг и вот это всё. Но это не отменяет очень кривой архитектуры. К примеру, CTFd зачем-то каждый запрос по-честному пересчитывает рейтинг при открытии /scoreboard, или грузит все таски из бд при открытии их списка, а следом ещё и догружает каждую из тасок при клике по ней. Надо ли говорить, что никто бы не умер, если бы главная страница / таски / скорборд / etc кешировались даже на 5 секунд? Сколько не поднимай лимиты, при целенаправленном DDoSe ручек того же скорборда борда всё равно бы легла даже при жёстких рейт-лимитах. Обидно, что я живу в обществе, где на 8 из 10 CTF используется CTFd.
Наверное, если я буду однажды проводить CTF, то обязательно займусь вайбкодингом своей борды — это, вероятно, будет работать стабильнее.

Так или иначе, CTF начался, и следующие пару часов я занимался решением всевозможных мелких технических проблем: фиксил и пушил таски, перезапускал ВМку с инстансером, когда нагрузка на неё приближалась к 100%, скейлил её, отвечал в чате, пару раз рестартил борду и вот это вот всё. После напряжённого старта я, наверное, ещё несколько часов не мог расслабиться, хотя со стороны всё шло хорошо.

К 17 часам всё стало сравнительно спокойно, инстансер перестал ложиться от нагрузки, и было только две проблемы: что я так и не выкатил один из вебов, и что у пары команд почему-то не запускались инстансы некоторых тасок (в этом, по всей видимости, тоже был виноват плагин; митигировалось это тем, что некоторым ю я просто поднял и выдал свои инстансы). Я даже успел сходить на пары по английскому, ахахах, и вернулся к 20.

Дальше по таймлайну где-то в районе 21 я заметил, что на борде прибавилось несколько тысяч пользователей. Я очень удивился, сходил в БД и увидел пачку новорегов вида solver_u123123@example.com. Решив не разбираться, я выпилил все эти учетки. Была ли это попытка брута флагов, желание положить борду, нейрослоп или что-то ещё — не знаю, и узнать уже нельзя. Так или иначе, я выключил свободную регистрацию, пожил так ещё час-два и вернул её. Больше инцидент не повторялся.

В 21 нам написали про первый и последний подтвержденный случай флагошера.

Возможно, если бы я сделал динамические флаги и ещё пару приколов, удалось бы вычислить больше шереров. Увынск, но не в этот раз, пока всё было на коленке...

Codex goes brrr

В 21:30 случилось первое недопустимое (зачеркнуто) событие: нам написали DUCKERZ и переслали логи кодекса, который... пробил борду.

В попытке решить таску он пришёл к выводу, что она нерешаема — и предложил поломать борду, а юзер это аппрувнул (вероятно, даже не специально). Кодекс нашел исходники многострадального докер-плагина, нашёл там очевидную (и похожую на бекдор) XSSку в имени команды на одной из страниц в админке, закинул полезную нагрузку и поймал момент, когда я её открыл. 

Вот мой последующий коммит с фиксом: https://github.com/goidactf/CTFd-Docker-X/commits/master/

А вот логи общения с кодексом, которые нам показали DUCKERZ: https://disk.360.yandex.ru/i/hocli7CTIlqmtg

Я снова проклял плагин, поныл в чатик оргов, что CTF мертв, и пошёл фиксить. Параллельно за 5 минут было принято совместное, но опрометчивое решение накинуть бонус в 3к поинтов как "багбаунти", однако мы быстро поняли (в том числе потому что нам активно подсказывали), что это было ошибкой.

Где-то за час мы изменили своё решение (которое почти всех устроило), обновили борду, и всё снова пришло в норму, хоть и осадочек, так сказать, остался.

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

Величайшей ошибкой было бы думать (зачеркнуто) спать... 

По графикам, где-то в 4 утра кто-то решил задосить инстансер (машинку с докером). Судя по скрину с зависшим htop, досили довольно очевидно: дергали хром в тех тасках, где я забыл добавить ограничения, или сделал их недостаточно жёсткими. Знали бы они, что положить всё можно было ещё проще, например, OOMнув машинку в таске с RCE... 

Поскольку инфрой занимался только я, другие орги не смогли ничего сделать. Я, конечно, скидывал креды от борды и машинки в общий чат, но никто не знал, что, где и как работает, и что, в общем-то, досят. 

Упав, машинка с докером унесла за собой и борду, потому что та заняла все доступные коннекты к бд, таймаутясь об лежащий докер. 

В 8 утра я проснулся по будильнику, рестартнул всё, и инфра поднялась.

Похожий дос повторился в ~11:40, но был купирован рестартом за 2 минуты.

скрин за секунду до смерти

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

Вместо итогов

Кто бы мог подумать, стоило делать всё вовремя и закладывать сильно больше времени на форс-мажоры. Это помогло бы избежать ряда технических проблем, а также анинтендедов.

CTFd нельзя использовать "как есть". Пока что я склоняюсь к тому, что на свою будущую потенциальную CTFку стоит написать свою борду. Или учиться грамотно скейлить и настраивать существующую; не отрицаю, что вполне может оказаться, что я просто за что-то не зашарил. 

Докер-плагин должен умереть. Он кривой, уже требовал ряда доработок, и требует ещё множества: гонки, отстутствие транзакций -> неконсистентности в БД и т. д. В конце концов, он оказался ещё и дырявым. Но, опять же, сходу другого хорошего и подходящего варианта нет.

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

Стоит ждать доса и защищаться от него превентивно, даже если вы проводите ноунейм СуньхуйвчайCTF. В частности, лимитировать ресурсы инстансов силами того же докера (cpu / ram), добавлять рейт-лимиты в таски, настраивать нормальный антидудос для борды и инстансера (силами того же облака). Стоило настроить и подтверждение почты на борде.

Не стоит недооценивать свой скилл ишью: как потом выяснилось, мы были в шаге от ещё одного пробива инфры через облачную метадату, которую я в Я.Облаке почему-то считал нормально захардененной по дефолту. Повезло, что в аккаунте не было сервисных учеток. Стоит обсуждать инфру всем вместе и не забивать на что-то из-за мыслей "ну вроде как это unexploitable". Надо было пять минут почитать доку. 

И самое главное — оказалось, вполне воможно провести прикольную стфку с буквально нулевым бюджетом и несколькими единомышленниками, даже если все заняты и всё будет делаться в последний момент. Буду рад, если наш опыт поможет кому-то избежать таких детских ошибок.

-----

P. S. Через две недели мы проведём финал, и это тоже будет абсолютно новый опыт для нас. Хочется верить, что хотя бы часть из описанных ошибок не повторится...

Report Page