Если пользователь может удалить чужую запись, в приложении нарушена объектная авторизация: сервер знает, кто отправил запрос, но не проверяет, разрешено ли этому человеку действие над конкретным заказом, файлом, сообщением или проектом. Скрытая кнопка и сложный URL не защищают данные.

Такую ошибку нужно устранять сразу. До исправления ограничьте опасную операцию, сохраните журналы и проверьте, не было ли несанкционированных удалений. Затем восстановите корректную проверку прав на сервере и добавьте тесты, которые не позволят проблеме вернуться.

Сначала снизьте риск

  • Временно отключите удаление для обычных пользователей или переведите его в режим soft delete.
  • Не очищайте access-логи, журнал действий и записи аудита.
  • Сделайте резервную копию базы перед массовым восстановлением данных.
  • Проверьте похожие операции: редактирование, скачивание, публикацию и смену владельца.
  • Если затронуты персональные или коммерческие данные, действуйте по внутреннему плану реагирования.

Не ограничивайтесь исправлением одной кнопки. Одинаковая ошибка часто повторяется в API, мобильном приложении, административных AJAX-методах и старых версиях endpoint.

Почему возникает проблема

Обычно запрос содержит идентификатор записи: сервер получает его, находит строку и удаляет. Авторизация проверяет только наличие сессии или токена. Принадлежность объекта пользователю, организации или разрешенному проекту не проверяется.

  • Контроллер доверяет ID из формы, URL или JSON.
  • Проверка прав выполнена только в интерфейсе JavaScript.
  • Роль пользователя проверяется, а принадлежность конкретной записи — нет.
  • Объект сначала загружается глобально, а не в области доступных пользователю данных.
  • Фоновая задача выполняет действие с системными правами без повторной проверки контекста.
  • Разные endpoint используют разные правила доступа.
  • После изменения модели ролей старый метод остался без защитного middleware.

Аутентификация и авторизация — разные проверки

Аутентификация отвечает на вопрос «кто это», а авторизация — «что этому пользователю разрешено». Действующая сессия не дает права удалять любую запись. Даже администратор подразделения может работать только в своей организации, а менеджер — только с назначенными проектами.

Для каждой операции нужно учитывать действие, объект и контекст: роль, владельца, организацию, состояние записи и дополнительные ограничения. Правило «пользователь авторизован» для удаления недостаточно.

Как воспроизвести ошибку безопасно

Проверяйте на тестовой среде и специально созданных учетных записях. Не используйте реальные чужие данные и не выполняйте destructive-проверки в рабочей базе.

  1. Создайте пользователя A и пользователя B в одной или разных организациях.
  2. Под каждой учетной записью создайте отдельную тестовую запись.
  3. От имени пользователя A запросите удаление его собственной записи и зафиксируйте ожидаемый успех.
  4. От имени пользователя A отправьте запрос к тестовой записи пользователя B.
  5. Ожидайте отказ без изменения данных и без раскрытия лишней информации.
  6. Повторите проверку для API, AJAX, мобильного клиента и массовых действий.

Корректный результат — ответ 403, если существование объекта можно раскрывать, либо 404, если объект должен быть невидим вне области доступа. Главное, чтобы запись не изменилась.

Загружайте объект только в разрешенной области

Надежнее не загружать запись глобально с последующей проверкой, а сразу ограничивать запрос областью текущего пользователя или организации. Тогда объект за пределами разрешенной области просто не будет найден.

  • Для личных данных добавляйте условие owner_id = current_user_id.
  • Для корпоративного кабинета ограничивайте выборку tenant_id текущей организации.
  • Для проекта проверяйте активное членство и право на удаление.
  • Для иерархии ролей учитывайте подразделение и область ответственности.
  • Для публичных объектов отдельно проверяйте право изменять или удалять, а не право просматривать.

Идентификатор владельца нельзя принимать из формы как достоверный. Его нужно получать из сессии, проверенного токена или уже авторизованного объекта.

Вынесите правила в единый слой авторизации

Если проверки разбросаны по контроллерам, одна из них неизбежно будет пропущена. Используйте policy, guard, permission-сервис или middleware, принятый в вашем фреймворке. Входная точка должна явно запрашивать право delete для конкретного объекта.

  • Правило не должно зависеть от того, показана ли кнопка в интерфейсе.
  • Одинаковая policy применяется в HTML-контроллере, API и фоновой задаче.
  • Системные роли не получают полный доступ без необходимости.
  • Смена владельца и массовое удаление имеют отдельные разрешения.
  • Решение об авторизации принимается до изменения базы и файлов.

Проверяйте организацию и изоляцию клиентов

В SaaS и корпоративных кабинетах проверки одного owner_id может быть недостаточно. Запись может принадлежать организации, а несколько сотрудников — иметь разные роли. Каждый запрос должен выполняться в контексте tenant, который определен сервером, а не передан клиентом без проверки.

  • tenant_id присутствует в таблицах, где нужна изоляция.
  • Все запросы к данным ограничены текущей организацией.
  • Составные уникальные индексы учитывают tenant_id.
  • Кеши и очереди включают tenant в ключ и payload.
  • Административный доступ между организациями включается явно и журналируется.

CSRF не заменяет проверку владельца

CSRF-токен защищает браузер от отправки нежелательного запроса с другого сайта, но не определяет право на объект. Запрос может иметь правильный CSRF-токен и при этом удалять чужую запись. Нужны обе проверки: защита от подделки запроса и объектная авторизация.

  • Изменяющие операции выполняются через POST, PATCH или DELETE, а не GET.
  • Для cookie-сессий проверяется CSRF-токен и политика SameSite.
  • Для API валидируется токен и его scope.
  • После этого отдельно проверяется право на конкретный объект.

Сделайте удаление обратимым

Soft delete не устраняет уязвимость, но уменьшает последствия ошибки. Вместо физического удаления запись получает дату удаления, автора действия и причину. Восстановление должно иметь собственное разрешение.

  • Обычные выборки исключают удаленные записи.
  • Связанные файлы не уничтожаются до завершения периода восстановления.
  • Окончательное удаление выполняется отдельной задачей по политике хранения.
  • Администратор видит, кто и когда удалил запись.
  • Конфиденциальные данные не хранятся бессрочно только ради возможности отката.

Добавьте журнал действий

Для критичных операций журнал должен отвечать на вопросы: кто выполнил действие, над каким объектом, когда, из какой организации, с каким результатом и по какому запросу. Не записывайте в лог пароли, токены и лишние персональные данные.

  • user_id и tenant_id;
  • тип и идентификатор объекта;
  • действие и результат авторизации;
  • время, request_id и безопасная часть сетевого контекста;
  • предыдущее состояние или ссылка на версию для восстановления;
  • причина отказа без раскрытия внутренней структуры клиенту.

Проверьте массовые и косвенные операции

После исправления одиночного удаления проверьте endpoint массового выбора, импорт, очистку корзины, удаление вложений, каскадные связи и фоновые очереди. Часто UI отправляет список ID, а сервер проверяет право только на первый объект или доверяет уже отфильтрованному списку.

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

Добавьте автоматические тесты

  • Владелец может удалить собственную тестовую запись.
  • Пользователь не может удалить запись другого пользователя.
  • Сотрудник одной организации не может воздействовать на объект другой.
  • Роль только для чтения не может удалить даже доступную для просмотра запись.
  • Администратор действует только в разрешенной области.
  • Удаленный, заблокированный или вышедший из проекта пользователь получает отказ.
  • Подмена ID, tenant_id или owner_id в запросе не меняет решение.
  • Массовая операция применяет правило к каждому объекту.

Такие проверки нужны на уровне HTTP и сервиса авторизации. Один модульный тест policy не обнаружит endpoint, который случайно обходит этот слой.

Как убедиться, что исправление работает

  1. Повторите безопасный сценарий с двумя тестовыми пользователями.
  2. Проверьте отказ и отсутствие изменений в базе, файлах и очередях.
  3. Убедитесь, что собственная запись удаляется по правилам.
  4. Проверьте роли, организации, API и массовые действия.
  5. Просмотрите журнал: разрешенные и запрещенные попытки должны различаться.
  6. Запустите регрессионные тесты и проверку зависимых функций.
  7. Только после этого верните операцию в рабочий интерфейс.

Типичные неправильные исправления

  • Спрятать кнопку удаления с помощью CSS или JavaScript.
  • Заменить числовой ID на UUID и считать его секретом.
  • Проверить только роль без владельца и организации.
  • Добавить CSRF-токен, но не проверить объект.
  • Исправить веб-страницу и оставить старый API без изменений.
  • Разрешить удаление всем сотрудникам одной организации без учета роли.
  • Возвращать отказ после того, как запись уже удалена.
  • Удалить журналы, не разобрав возможные прошлые операции.

Профилактика

Зафиксируйте матрицу ролей и операций, используйте deny by default и проверяйте объект в каждом изменяющем endpoint. При code review отдельно рассматривайте доступ к данным, а в CI запускайте негативные тесты для пользователей из разных организаций.

  • Единый сервис авторизации вместо ручных условий в контроллерах.
  • Минимальные права для сервисных аккаунтов и фоновых задач.
  • Регулярный аудит endpoint, включая устаревшие версии API.
  • Мониторинг необычного числа удалений и повторных отказов.
  • Проверяемая процедура восстановления и ограниченный срок хранения копий.

Итог

Если пользователь может удалить чужую запись, сервер проверяет личность, но не право на объект. Исправление состоит из ограничения выборки областью пользователя или организации, централизованной policy, защиты изменяющего запроса, обратимого удаления, аудита и негативных тестов.

Если нужно найти и закрыть такую ошибку, я могу проверить серверные endpoint, модель ролей и изоляцию организаций, внедрить объектную авторизацию и журнал действий, подготовить безопасное восстановление и регрессионные тесты без работы с реальными чужими данными.