Разбор: 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.