Перейти к содержанию
Innovialabs

Нарушение контроля доступа и IDOR: как найти и исправить

Практика4 мин

Коротко

IDOR — это когда сервер отдаёт объект по ID, не проверив, принадлежит ли он текущему пользователю. Лечится проверкой прав на сервере для каждого запроса, выборками с фильтром по владельцу и автотестами вида «пользователь B не видит данные A».

Что такое IDOR

Broken Access Control — общее название ошибок, при которых пользователь может сделать то, на что у него нет прав. IDOR (Insecure Direct Object Reference) — самый частый её вид: сервер находит объект по идентификатору из запроса и отдаёт его, не проверив, чей он.

Классический пример. Пользователь открывает свой счёт:

GET /api/invoices/1042

Меняет число на 1041 — и видит чужой счёт с именем, адресом и суммой. Никакого взлома: обычный браузер и одна цифра.

То же самое работает для изменения и удаления (PUT /api/orders/77), загрузки файлов (/files/report-883.pdf), параметров в теле запроса ("user_id": 15) и скрытых полей формы.

Почему это риск номер один в OWASP Top 10

В OWASP Top 10 нарушение контроля доступа стоит на первом месте (A01). Причины практические:

  • Фреймворк не сделает это за вас. Аутентификация подключается одной строкой, а правило «счёт видит только его владелец и бухгалтер компании» знает только ваш код.
  • Проверку нужно повторить в каждом эндпоинте. Достаточно забыть её в одном, например в экспорте в CSV.
  • Сканеры её почти не находят: они не знают, какие данные кому принадлежат.
  • Фронтенд создаёт ложное чувство защиты: кнопки «Удалить» нет в интерфейсе, но API принимает запрос.

Виды нарушений

ВидПример
Горизонтальная эскалацияклиент читает заказы другого клиента
Вертикальная эскалацияобычный пользователь вызывает /admin/users
Мультитенантностьсотрудник компании A видит данные компании B
Mass assignmentв теле запроса передают "role": "admin", и сервер его сохраняет
Обход через методыGET проверен, а DELETE на тот же ресурс — нет

Как исправить

Фильтруйте выборку по владельцу

Не «найти по ID, потом проверить», а сразу искать в пределах доступного пользователю:

// Плохо: отдаёт любой счёт
const invoice = await db.invoice.findUnique({ where: { id } });

// Хорошо: ищем только среди счетов текущего пользователя
const invoice = await db.invoice.findFirst({
  where: { id, ownerId: session.userId },
});
if (!invoice) return res.status(404).end();

Для мультитенантных систем то же правило с tenantId в каждом запросе. В PostgreSQL можно добавить Row-Level Security как страховочный слой на уровне базы.

Централизуйте правила

Правила доступа не должны быть размазаны по контроллерам. Вынесите их в одно место — политики, middleware, функции вроде can(user, "edit", invoice). Так их проще читать, проверять на ревью и тестировать.

Запрещайте по умолчанию

Новый эндпоинт без явного правила должен быть закрыт, а не открыт. Роль и права берите из серверной сессии, а не из параметров запроса или JWT без проверки подписи.

Не путайте скрытие с защитой

  • UUID вместо числовых ID усложняет перебор, но не заменяет проверку: ID утекают в ссылках, письмах и логах.
  • Отсутствие кнопки в интерфейсе ничего не защищает.
  • Разрешайте для записи только явный список полей, чтобы закрыть mass assignment.

Отвечать 404 вместо 403 для чужих объектов — нормальная практика: так атакующий не узнаёт, существует ли объект.

Как искать и тестировать

  1. Два пользователя, один сценарий. Создайте A и B, выполните действия от A, затем повторите запросы с сессией B. Каждый успешный ответ — находка.
  2. Матрица доступа. Таблица «роль × эндпоинт × метод» с ожидаемым результатом. Она же становится основой тестов.
  3. Автотесты на отказ. Для каждого ресурса — тест, что чужой пользователь получает 404 или 403 на чтение, изменение и удаление.
  4. Ревью кода. Ищите запросы к базе по ID без условия на владельца или тенанта.
  5. Логирование отказов. Всплеск 403/404 от одного аккаунта — признак перебора.
it("user B cannot read user A's invoice", async () => {
  const invoice = await createInvoice(userA);
  const res = await api.as(userB).get(`/api/invoices/${invoice.id}`);
  expect(res.status).toBe(404);
});

Подробные рекомендации — в OWASP Authorization Cheat Sheet.

Частые вопросы

Только частично. Угадать UUID сложно, но он может утечь через ссылки, письма, логи или другой эндпоинт. Проверка прав на сервере всё равно обязательна.

Поможем с этим

Если хотите не разбираться самим, а получить результат — этим мы занимаемся каждый день.

Обсудить проект

Читайте также

Есть идея или задача? Расскажите нам

Ответим в течение рабочего дня и предложим решение. Консультация бесплатная.