Безопасность API (OWASP API Top 10)
В мире API дыра номер один — не «взлом шифрования», а проверка прав, которую забыли сделать на сервере.
API (Application Programming Interface) — интерфейс, через который приложения обмениваются данными (обычно REST/JSON или GraphQL). OWASP API Security Top 10 — список самых частых классов уязвимостей именно у API, отдельный от классического веб-Top-10.
Мобильные приложения, SPA и интеграции общаются с бэкендом через API, и атакующий видит ровно те же запросы, что и легальный клиент: их легко перехватить и повторить с изменёнными параметрами. Защитнику важно понять три самых распространённых и болезненных класса из OWASP API Top 10 — нарушение авторизации на уровне объекта, чрезмерную выдачу данных и отсутствие лимитов — и научиться закрывать их на сервере. Главный принцип: клиенту доверять нельзя, всё проверяется на бэкенде.
Зачем это знать защитнику
API-уязвимости массовы, потому что фронтенд «выглядит правильно», а проверки на сервере нет. Понимая логику этих ошибок, вы добавляете недостающие проверки до того, как их найдёт кто-то снаружи. Проверять можно только свои API и легальные тренировочные стенды (juice-shop, DVWA, TryHackMe) либо пентест с письменным согласием владельца. Дёргать чужой прод — несанкционированный доступ (ст. 272 УК РФ).
API1: Broken Object Level Authorization (BOLA / IDOR)
Самый частый и самый критичный класс. Эндпоинт принимает идентификатор объекта (/api/orders/1024) и отдаёт его, проверив, что пользователь залогинен, но забыв проверить, что объект принадлежит именно ему. Атакующий просто меняет 1024 на соседний номер и читает чужие заказы.
Как это возникает в коде
# УЯЗВИМО: проверена аутентификация, но не владение объектом
@app.get("/api/orders/{order_id}")
def get_order(order_id: int, user = Depends(current_user)):
return db.orders.get(order_id) # вернёт ЛЮБОЙ заказ по id
Как защититься
Проверка владения должна быть в самом запросе к данным — фильтруйте по владельцу, а не «достал и отдал»:
# БЕЗОПАСНО: объект ищется в рамках владельца
@app.get("/api/orders/{order_id}")
def get_order(order_id: int, user = Depends(current_user)):
order = db.orders.get(order_id, owner_id=user.id) # привязка к владельцу
if order is None:
raise HTTPException(404) # не существует ИЛИ не ваш — одинаково
return order
Дополнительно: используйте непредсказуемые идентификаторы (UUID вместо последовательных чисел) — это не замена проверке прав, но усложняет перебор. И отдавайте 404 вместо 403, чтобы не подтверждать существование чужих объектов.
API3: Excessive Data Exposure (чрезмерная выдача)
Бэкенд возвращает весь объект целиком — «фронтенд сам покажет только нужное». Но фильтрация на клиенте не защищает данные: в ответе по сети едут passwordHash, внутренние флаги, e-mail и телефоны других пользователей. Атакующий смотрит сырой JSON, а не отрисованную страницу.
Как защититься
Сервер отдаёт только те поля, что нужны клиенту, через явные схемы ответа (DTO/serializer), а не «модель как есть»:
# Явная схема ответа: лишние поля просто не попадут наружу
class UserPublic(BaseModel):
id: int
name: str
# password_hash, email, is_admin — НЕ включены
@app.get("/api/users/{uid}", response_model=UserPublic)
def get_user(uid: int):
return db.users.get(uid) # вернутся только id и name
Правило: allow-list полей (явно перечислить, что отдаём), а не deny-list (пытаться вычеркнуть лишнее — однажды забудете). То же касается массовых выдач (/api/users): отдавайте публичный минимум.
API4: Unrestricted Resource Consumption (нет лимитов)
Если эндпоинт можно дёргать без ограничений, его используют для перебора паролей и кодов из SMS, парсинга всей базы постранично и отказа в обслуживании. Особенно опасны эндпоинты входа, восстановления пароля и подтверждения OTP.
Как защититься
| Мера | Что закрывает |
| rate limiting (лимит запросов в минуту) | перебор, DoS |
пагинация с потолком limit | выкачивание всей базы за раз |
| лимит размера тела запроса | перегрузку памяти большими payload |
| блокировка/задержка после N неудач | brute-force логина и OTP |
# Идея ответа сервера при превышении лимита
HTTP/1.1 429 Too Many Requests
Retry-After: 60
Как это работает под капотом
API без состояния (stateless): каждый запрос несёт токен, и сервер на каждый запрос обязан заново ответить на два вопроса — «кто это?» (аутентификация) и «можно ли ему именно это с именно этим объектом?» (авторизация). BOLA — это пропущенная проверка второго вопроса для конкретного объекта. Excessive Data Exposure — лишние поля в ответе, потому что сериализуется вся модель. Отсутствие лимитов — нет учёта частоты и объёма запросов. Сюда же примыкает ещё один важный класс из списка — Broken Function Level Authorization (BFLA): когда обычному пользователю доступны административные действия (например, DELETE /api/users/{id}), потому что права на уровне функции/метода не проверяются. Лечится это так же — явной проверкой роли и прав на сервере для каждого действия, а не сокрытием кнопки в интерфейсе. Всё это — серверные пробелы, и закрываются они только на сервере: клиент в защите не участвует.
Как защититься
- Проверяйте владение объектом на каждом эндпоинте: ищите данные в рамках текущего пользователя, отдавайте
404для чужого. - Отдавайте только нужные поля через явные схемы ответа (allow-list), никогда не «модель целиком».
- Ставьте rate limiting, пагинацию с потолком и лимиты размера запроса; усиливайте защиту на логине/OTP.
- Валидируйте вход на сервере, версионируйте API, ведите аудит-логи и алёрты на аномалии (всплеск 404/401, перебор id).
- Регулярно прогоняйте свой API сканером (например, OWASP ZAP) на своём стенде, а не на чужом проде.
Итоги
- OWASP API Top 10 — отдельный список рисков именно для API; лидер — BOLA (проверка прав на объект).
- Три ключевых класса: BOLA/IDOR, чрезмерная выдача данных, отсутствие лимитов.
- Общий корень — доверие к клиенту; лекарство — все проверки на сервере.
- Защита: владение объектом на каждом запросе, allow-list полей в ответе, rate limiting и пагинация.
- Тестируйте только свои API и легальные стенды; чужой прод — это ст. 272 УК РФ.