Access Control: Горизонтальная эскалация через API в Express
API-эндпоинт /api/users/:user_id/notes не проверяет владельца. Получите заметки администратора.
mediumnodejsPro
Задача
# Горизонтальная эскалация: чужие ресурсы через REST API
## Сценарий
Перед вами Flask-приложение интернет-магазина SecureShop. У каждого пользователя есть личные заметки, доступные через REST API: фронтенд приложения берёт идентификатор текущего пользователя из контекста и запрашивает его заметки у бэкенда; пользователь видит свои заметки, чужие не видит. С точки зрения интерфейса всё выглядит правильно.
Реализация на стороне сервера, однако, имеет типовую ошибку авторизации: API-эндпоинт чтения заметок принимает идентификатор пользователя **из самого URL-запроса** и **не сверяет** его с идентификатором аутентифицированного пользователя. То есть сервер требует «вы должны быть авторизованы» (а это значит — есть валидная сессия), но не проверяет «вы можете обращаться **к этому конкретному ресурсу**». Любой авторизованный пользователь, изменив значение идентификатора в URL, начинает получать заметки **другого** пользователя — это и есть горизонтальная эскалация привилегий (известная также как Broken Object-Level Authorization, OWASP API1).
В системе есть привилегированный пользователь (администратор), и в его заметках хранится CTF-флаг.
## Цели
1. Войдите как обычный пользователь `demo / demo` и найдите эндпоинт REST API, который возвращает заметки пользователя. Подсказка: посмотрите HTTP-запросы в DevTools при открытии страницы со своими заметками — вызов API будет содержать ваш идентификатор в URL.
2. Подтвердите, что подмена этого идентификатора на чужой возвращает не 403, а **заметки другого пользователя**. Это и есть уязвимость.
3. Найдите идентификатор административного пользователя (низкие числовые значения часто соответствуют ранним/привилегированным аккаунтам — попробуйте перебор с малых значений), прочитайте его заметки и заберите CTF-флаг из их содержимого.
## Данные
| Параметр | Значение |
|----------|----------|
| Обычный пользователь | `demo` / `demo` |