Кубок CTF России

Разбор: Peridromofilia с отбора X Кубка CTF

Условие: web, раздатки нет. Сайт клуба на Flask и mongoengine: карта участника, журнал поездок, обмен. По таблице организаторов задание решили 213 раз.

Что видно снаружи

  • Страница /members показывает внутренние коды карт, в том числе ServiceCard
  • Ошибки на PATCH /api/profile выдают mongoengine
  • Рабочий синтаксис set__profile__… показывает, что тело запроса уходит в QuerySet.update()

Уязвимость

Это mass assignment: всё незнакомое из тела запроса уходит в обновление как есть. Прямая подмена класса валидацию не проходит, но есть обход __raw__, который пишет сырые данные в MongoDB мимо модели. Профиль хранит служебное поле _cls, и при чтении он резолвится через общий реестр классов без проверки иерархии.

Ход решения

  • Отправить PATCH /api/profile с __raw__ и _cls равным ServiceCard
  • Открыть /me: статус карты печатает метод badge() этого класса, и там служебный доступ с флагом

Что вынести

Служебные поля ORM — такая же цель, как и обычные. Тело запроса перед обновлением модели надо ограничивать списком разрешённых полей.

Пример

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

Важно. Окно здесь есть только в embedded-поле: у документов верхнего уровня запрос добавляет фильтр по _cls. Такие детали и отличают разбор от готового рецепта.

Для разбора в команде

  • ☐ Выписаны признаки библиотеки
  • ☐ Найден вход в update()
  • ☐ Понятно, почему сработал __raw__
  • ☐ Записан способ защиты

Что заметили снаружи и как защититься, запишите в шаблон разбора 👇

Основание: условие и решение авторов задачи, репозиторий acisoru/ctfcup2026-quals, web/peridromofilia. Канал не принадлежит организаторам; официальный сайт — ctfcup.ru.

Первоисточник