Админка по ?isAdmin=true
"Админка готова, доступ только у админов":
app.get('/admin/users', async (req, res) => {
if (req.query.isAdmin === 'true') {
return res.json(await getAllUsers());
}
return res.status(403).json({ error: 'Forbidden' });
});
На стенде с ?isAdmin=true в ссылке из фронта всё работает. Джун делает вывод: "каждый админ имеет доступ".
Уязвимость — Broken Access Control (OWASP Top 10, #1) — права решаются данными с клиента, а не ролью в сессии, JWT или записью в БД на сервере. Любой пользователь (и аноним, если маршрут открыт) может подставить тот же параметр.
RBAC и различие AuthN / AuthZ — аутентификация и авторизация. Каталог IDOR и API-угроз — 8.07 / 128.
Эксплуатация за пять секунд
GET /admin/users?isAdmin=true HTTP/1.1
Host: api.example.com
В браузере:
https://api.example.com/admin/users?isAdmin=true
Ответ — полный список пользователей из getAllUsers() — email, id, внутренние поля. Без логина, если эндпоинт не закрыт middleware auth.
В curl:
curl -s "https://api.example.com/admin/users?isAdmin=true"
Сервер честно отдаёт 403 без параметра — это создаёт иллюзию защиты. Проверка "есть ли флаг админа" и проверка "кто этот админ по данным сервера" — разные задачи. Здесь реализована только первая, и флаг контролирует клиент.
Откуда берётся ошибка
Частые варианты той же логики:
| "Проверка" на сервере | Что подставляет атакующий |
|---|---|
req.query.isAdmin === 'true' | ?isAdmin=true |
req.body.role === 'admin' | {"role":"admin"} в JSON |
req.cookies.admin === '1' | Cookie admin=1 в DevTools |
req.headers['x-admin'] | X-Admin: true |
Фронт: if (user.isAdmin) fetch(...) | Прямой вызов API без фронта |
Фронтенд может прятать кнопку "Админка". API обязан проверять права сам — по серверному контексту после auth.
Как должно быть
- Аутентификация — middleware
auth: естьreq.user/req.userId. - Авторизация — роль из подписанного источника (JWT claim
roles, поле в сессии после login, lookup в БД), не из query.
app.get('/admin/users', auth, requireRole('admin'), async (req, res) => {
return res.json(await getAllUsers());
});
function requireRole(role) {
return (req, res, next) => {
const roles = req.user.roles ?? [];
if (!roles.includes(role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
Для JWT — claim realm_access.roles / groups после проверки подписи (см. JWT — семь строк). Для сессии — роль записана на сервере при входе, клиент её не присылает заново.
Тест: обычный пользователь с валидным токеном вызывает /admin/users без query — только 403. Подстановка ?isAdmin=true ничего не меняет.
Родственные промахи
- IDOR —
GET /users/123отдаёт чужой профиль, если не проверятьowner_id === req.user.id. Тот же класс: "есть токен" ≠ "можно этот объект". - Mass assignment —
PATCH /profileс{"isAdmin":true}обновляет роль, если сервер не фильтрует поля. - Путь только на фронте —
/adminв React Router без guard на API.
Все они лечатся единой политикой на сервере для каждого эндпоинта.
Чек-лист
- Ни один query, body, cookie или header не выступает источником прав.
- Админские маршруты за
auth+ проверка роли / permission. - Роль в JWT подписана IdP; подделка payload без ключа невозможна.
- Интеграционные тесты: user и admin, без подстановки "секретных" параметров.
- Сканер/Burp по чек-листу OWASP API — каталог угроз.
Связанные материалы
| Тема | Статья |
|---|---|
| RBAC, ABAC | Аутентификация и авторизация |
| IDOR и Broken Access Control | 8.07 / 128 |
| Подделка роли в JWT | JWT — семь строк |
| Mass assignment | 8.07 / 128 — каталог |