Broken Access Control: как QA тестировать права доступа (топ-1 риск OWASP)
Самый частый способ увидеть чужие данные — не хитрый взлом, а замена одной цифры в URL. Открываешь свой заказ по адресу /orders/1043, меняешь на /orders/1044 — и видишь чужой заказ с именем, адресом и телефоном. Это не выдуманный сценарий: сломанный контроль доступа стоит на первом месте в OWASP Top 10, и что приятно для нас — большую часть таких дыр QA ловит обычными руками, без пентестера и спецтулов. Нужно только знать, куда тыкать.
Контроль доступа отвечает на вопрос «этому пользователю можно делать это действие с этим объектом?». Ломается он там, где проверку либо забыли, либо сделали только на клиенте. Разберём по типам.
IDOR — подставь чужой идентификатор
IDOR (Insecure Direct Object Reference) — когда объект достаётся по идентификатору из запроса, а сервер не проверяет, твой ли это объект. Классика: id в URL, в теле запроса, в скрытом поле формы, в куке.
Как тестировать: заведи двух пользователей (A и B). Под A открой свой ресурс — заказ, документ, профиль, файл — и запомни его id. Теперь под сессией B (или вообще без авторизации) запроси этот же id. Должно быть 403/404, а не чужие данные. Проверяй не только цифровые id — GUID и хэши тоже не защита, если их можно узнать (в письме, в логах, в чужой ссылке).
Где искать: скачивание файлов (/download?file=...), просмотр заказа/брони/тикета, API вида /api/users/{id}, экспорт, инвойсы, аватары, вложения.
Горизонтальная и вертикальная эскалация
Две разные вещи, проверять надо обе:
- Горизонтальная — доступ к данным пользователя того же уровня. Ты обычный юзер и лезешь в данные другого обычного юзера (тот самый IDOR по сути). «Я вижу чужой профиль/заказ».
- Вертикальная — обычный пользователь выполняет действие уровнем выше. «Я обычный юзер, но дёргаю админский эндпоинт и меняю чужую роль / удаляю пользователя / вижу админку». Это опаснее.
Тест вертикальной: залогинься обычным юзером, найди (по докам, по трафику админа, по угадыванию) админское действие и повтори его своей сессией. Права должны резаться на сервере, а не тем, что «в меню кнопки нет».
Forced browsing — кнопки нет, а ручка работает
Отсутствие ссылки в интерфейсе — это не защита. Прямой заход по адресу — базовый тест:
- Открой
/admin,/settings/users,/reportsнапрямую, будучи обычным юзером или гостем. - Дёрни API-эндпоинт, который UI показывает только админам.
- Зайди на «страницу успеха» или шаг мастера в обход предыдущих шагов.
Если контент отдаётся — доступ битый, даже если «в интерфейсе туда не попасть».
Проверяй на бэке, а не в UI
Главная ловушка QA: проверить, что кнопка «Удалить» спрятана у обычного юзера, и на этом успокоиться. Спрятать элемент ≠ закрыть доступ. UI-ограничения — это удобство, а не безопасность. Настоящая проверка — дёрнуть сам запрос:
- Повтори действие напрямую (через DevTools → Network → «Copy as fetch/cURL», Postman, Proxyman/Charles).
- Убери/поменяй параметр, который «должен» ограничивать (
role=user→role=admin,isAdmin=false→true). - Отправь запрос, которого в твоём UI нет вообще, но он есть у роли выше.
Если сервер выполнил — дыра, независимо от того, что показывал интерфейс.
Токены, куки и роли
- Чужой/старый токен: подставь токен другого пользователя — доступ должен быть закрыт. Протухший или отозванный токен не должен работать (проверь logout и смену пароля — старые сессии обязаны инвалидироваться).
- Смена роли на лету: если роль понизили/забанили, активная сессия должна перестать пускать — а не «до перелогина».
- Подмена в токене: если это JWT — проверь, что сервер валидирует подпись, а не верит полю
roleиз payload; сменаalgнаnoneне должна прокатывать. - Границы арендатора (multi-tenant): в B2B проверь, что пользователь одной компании не видит данные другой, подставив чужой
tenant_id/org_id.
Матрица ролей — база для тестов
Собери простую таблицу: строки — действия/ресурсы, столбцы — роли (гость, юзер, чужой юзер, менеджер, админ). В клетке — «можно/нельзя». Каждую «нельзя» превращаешь в негативный тест: делаешь запрос из-под этой роли и ждёшь 403. Такая матрица разом покрывает и горизонталь, и вертикаль, и forced browsing — и не даёт забыть роль.
Чек-лист
- Заведены минимум два пользователя (A и B) + гость для кросс-проверок.
- IDOR: подстановка чужого
id(URL, тело, скрытые поля, куки) →403/404. - GUID/хэш-id тоже проверены (не защита, если утекают).
- Горизонталь: доступ к данным равного пользователя закрыт.
- Вертикаль: обычный юзер не выполняет админские действия через API.
- Forced browsing: прямой заход на
/adminи скрытые эндпоинты → отказ. - Проверка на бэке: действие повторено запросом в обход UI, не только «кнопка спрятана».
- Подмена параметров роли/флагов (
role,isAdmin) не даёт прав. - Чужой/протухший/отозванный токен не работает; logout инвалидирует сессию.
- JWT: подпись валидируется,
alg:noneне проходит,roleиз payload не доверяется. - Multi-tenant: изоляция между компаниями/организациями.
- Есть матрица «роль × действие», каждая «нельзя» покрыта негативным тестом.
Типовые места, где ломается
- Эндпоинты, добавленные позже основного функционала («быстро прикрутили экспорт»).
- Массовые операции и batch-API (проверку ставят на один объект, забывают на список).
- Прямые ссылки на файлы и вложения в обход приложения.
- «Служебные» и debug-эндпоинты, забытые в проде.
- GraphQL/агрегирующие запросы, где авторизация на верхнем поле есть, а на вложенном — нет.
Коротко — что забрать с собой
- Broken Access Control — №1 в OWASP, и QA ловит его руками без пентестера.
- Главный тест — повторить запрос из-под другой роли/пользователя и ждать
403, а не смотреть на UI. - IDOR, горизонталь, вертикаль, forced browsing — четыре обязательных класса проверок.
- Спрятанная кнопка — не защита; истина на бэке.
- Матрица «роль × действие» превращает хаос в набор негативных тестов.
Почитать: OWASP — A01 Broken Access Control · OWASP Web Security Testing Guide · OWASP — IDOR Prevention Cheat Sheet · PortSwigger — Access control vulnerabilities