Небезопасный Сброс Пароля (IDOR)
Форма сброса пароля принимает user_id из клиента, позволяя атакующему сбросить пароль любого пользователя через IDOR.
mediumgolangPro
Задача
# Auth: захват аккаунта через небезопасный сброс пароля (IDOR)
## Сценарий
Вы аудируете интернет-магазин **SecureShop**. У приложения есть стандартный workflow восстановления пароля: пользователь вводит логин на странице «Forgot Password», получает email с одноразовым ссылкой-токеном, переходит по ней и устанавливает новый пароль. Команда уверена, что workflow безопасен — токен генерируется случайно, проверяется на существование, удаляется после использования. По данным внутреннего аудита, в админ-панели хранится конфиденциальная метка, доступная только admin-учётке; её требуется получить, имея только обычную учётную запись.
## Цель
1. Изучите параметры финального POST-запроса подтверждения сброса пароля: какие поля передаются с клиента, какие из них контролирует пользователь, какие проверяются сервером.
2. Найдите параметр, который сервер принимает «как есть», без серверной валидации соответствия другим полям — параметр, через который атакующий мог бы повлиять на ход исполнения.
3. Используя обнаруженный канал, захватите admin-аккаунт и извлеките флаг из админ-панели.
## Теория
Workflow сброса пароля через email-токен — стандартный паттерн в современных приложениях, и он же — частый источник IDOR-уязвимостей. Корректная реализация хранит **серверную связь** между токеном и идентификатором пользователя (либо в БД через `reset_tokens(token, user_id)`, либо в самом токене через криптоподпись HMAC) и при подтверждении сброса извлекает `user_id` **исключительно из этой серверной связи**. Уязвимая реализация — частая ошибка — принимает `user_id` (или email, или username) **из тела POST-запроса** клиента, считая, что «пользователь же только что заполнил форму, как ещё его идентифицировать».
**Уязвимый паттерн:**
```go
// Сервер доверяет user_id из тела клиентского запроса при сбросе пароля
token := r.FormValue("token")
userID := r.FormValue("user_id") // <-- контролируется атакующим
newPassword := r.FormValue("new_password")
db.Exec("SELECT 1 FROM reset_tokens WHERE token=?", token) // проверка существования токена
db.Exec("UPDATE users SET password=? WHERE id=?", newPassword, userID) // на любого user_id!
```
Атака предельно проста: атакующий запрашивает сброс пароля **для своего собственного** аккаунта, получает валидный токен, открывает форму подтверждения, изменяет hidden-поле `user_id` (или эквивалент в JSON-теле) на ID администратора, и отправляет форму с новым паролем. Сервер «принимает» токен (он валиден — для аккаунта атакующего), но обновляет пароль уже у admin-учётки.
## Точка входа атаки
| Параметр | Значение |
|----------|----------|
| Учётные данные | `demo` / `demo` |
| Запрос сброса | `POST /forgot-password` |
| Подтверждение сброса | `POST /reset` (форма с `token`, `user_id`, `new_password`) |
| Встроенный почтовый ящик (для своих писем) | `/email` |
| Привилегированный эндпоинт | `GET /admin` |