Райтап на задачу «YOFA 1.0 – grep challenge»

dikiyponos

Автор:

dikiyponos

31 октября 2026

5

31.10.2026
YOFA 1.0 – grep challenge
Категория:

Web

Задача «YOFA 1.0 – grep challenge»

tears

Автор:

tears

Первая кровь:
dnr0v3c
Сложность:
baby
Дата публикации:
07.02.2026
Решений:
84
Рейтинг:
5/5
Ссылка:
Решать

Скачиваем исходники таски и запускаем собственный инстанс.

Рассмотрим структуру проекта с помощью tree. Сразу можно обратить внимание на папку config, там есть файлы, связанные с инициализацией базы данных. Зачастую там бывает пароль администратора.

Структура исходного проекта

Открываем любым текстовым редактором config/init-db.js и смотрим:

Пароль администратора в init-db.js

Оказывается adminPassword лежит в исходниках. Открываем инстанс и входим под пользователем admin с этим паролем.

Вход в качестве администратора

Теперь мы администратор.

Мы можем пойти по простому пути и сразу получить флаг: для этого надо немножко почитать исходники и понять, где он вообще должен лежать. Откроем controllers/adminController.js, routes/admin.js.

Обработчик выдачи флага
Маршруты административного раздела

И видим, что только администратору откроется /admin/flag, перейдём туда.

Флаг в административном разделе

Но не кажется ли это слишком просто? Рассмотрим решение по задумке автора.

Полазив по сайту и исходникам, можем найти, что есть некий бот (/report), который переходит по localhost-ссылкам с админской кукой.

Бот обработки жалоб

Значит, надо как-то заставить бота открыть страницу сайта, чтобы бот сделал что-то пакостное, ну или сделал обычного пользователя админом. Как хорошо, что есть метод /admin/addAdmin (см. userController.js)

Метод добавления администратора

Теперь фокусируемся на анализе самих страниц сайта. В большинстве EJS-файлов в POST-запросах URL и параметры фиксированы, кроме одного — profile.ejs.

Параметры профиля на клиенте

Рассмотрим этот код. Видим urlParams.getAll(“id”) – параметр возвращает массив. Также видим, что берётся последний элемент и подставляется в URL без проверки. В теле уже есть _csrf токен и userId.

Теперь рассмотрим серверную часть. В routes/user.js мы видим, что нам нужен userController.js, откроем его.

Разбор идентификатора профиля на сервере

Пятая строка на скриншоте является ключевой. Если передать два параметра id: /user/profile?id=2&id=../admin/addAdmin то express.js разберёт этот дубликат как массив ["2", "../admin/addAdmin"]

Далее parseInt приводит массив к строке и прочитает число до первого нецифрового символа: parseInt("2,../admin/addAdmin") – то есть вернёт 2 в этом примере.

Получается, что сервер берёт первый id, а клиент в profile.ejs — последний. Это client-side parameter pollution.

Какой же теперь у нас план? Регистрируем своего пользователя и запоминаем его id. Он виден сразу после входа.

Идентификатор зарегистрированного пользователя

Запомним, что наш id = 3. Теперь идём в /report, чтобы отправить боту свою очень злую ссылку.

Форма отправки ссылки боту

Как мы ее составим? Мы знаем, что кука админа привязана к localhost, но не знаем даже порта. Смотрим docker-compose.yaml в корне и видим.

Порт сервиса в docker-compose

Оно работает на порту 3000. Составляем URL:

http://localhost:3000/user/profile/?id=3&id=../admin/addAdmin

Не забудьте только поменять первый id на свой собственный.

Логинимся под своим пользователем, переходим на /report, вводим туда эту ссылку и ждём.

После отправки бот с админской кукой открывает страницу. Сервер рендерит наш профиль по первому id, а в body наш userId + валидный _csrf. Клиент берёт последний id, делает fetch("/log/"+profileId), браузер нормализует путь в POST /admin/addAdmin.

/admin/addAdmin видит админский JWT бота, берёт userId из body и делает нас админом. Поле action ему пофиг.

Теперь остаётся только перезайти, а то старый токен ещё обычного пользователя, — и вуаля, вы – админ. Остаётся перейти на /admin/flag, чтобы получить его.

Флаг после повышения привилегий

Таким образом, цепочка такая:

  1. SSRF (Server Side Request Forgery) - /report открывает любую http localhost ссылку.

  2. Client-side parameter pollution – клиент и сервер берут разные id.

  3. Path traversal в клиентском fetch (..)

  4. Privilege Escalation - /admin/addAdmin принимает userId из тела и делает его админом.

А CSRF никак и не нужно обходить, страницы сами всё за нас делают.

Если вы нашли опечатку или ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter

Также по теме:

Райтап на задачу «Бесконечный бензин»

/Разборы задач

Райтап на задачу «Бесконечный бензин»

Изучаем настройки АЗС BugOil и используем prototype pollution, чтобы обойти лимит выдачи топлива.
Прочитать

4

Райтап на задачу «Дуглас Муглис»

/Разборы задач

Райтап на задачу «Дуглас Муглис»

Исследуем подозрительный WebSocket и расшифровываем данные, отправляемые JavaScript-скиммером.
Прочитать

4