Безопасность API: OWASP, CORS, HTTPS
API — это дверь в ваши данные; безопасность решает, кого и куда она пускает.
OWASP API Security Top 10 — список самых частых и опасных уязвимостей API, составленный сообществом OWASP как ориентир для защиты.
В отличие от классических сайтов, API отдаёт данные машинам и часто раскрывает структуру бэкенда напрямую. Атакующему не нужно «обходить интерфейс» — он шлёт запросы вручную. Поэтому каждое предположение «клиент так делать не будет» — потенциальная дыра. Этот урок собирает ключевые угрозы и базовые меры: что проверять, что шифровать и как безопасно пускать браузерные запросы с других доменов.
OWASP API Security Top 10: ключевые угрозы
| Угроза | Суть |
| BOLA (Broken Object Level Authorization) | сервер не проверяет, что объект принадлежит запросившему: GET /orders/777 отдаёт чужой заказ |
| Broken Authentication | слабая проверка личности: предсказуемые токены, отсутствие истечения, утечка ключей |
| Excessive Data Exposure | ответ отдаёт лишние поля (хэш пароля, внутренние флаги), полагаясь на то, что «фронт их не покажет» |
| Mass Assignment | клиент шлёт лишние поля ("role":"admin"), и сервер слепо их сохраняет |
| Lack of Rate Limiting | нет защиты от перебора и флуда (см. урок про rate limiting) |
BOLA — угроза номер один: чаще всего ломаются именно из-за неё. Пример атаки — подстановка чужого id:
# Токен мой, но прошу чужой заказ
curl https://api.example.com/v1/orders/777 \
-H "Authorization: Bearer <my_token>"
Если сервер не проверит order.owner == current_user, он отдаст чужие данные. Лечится проверкой владения на каждом доступе к объекту, а не только наличием валидного токена.
Excessive data exposure и mass assignment
Эти две угрозы зеркальны. Первая — лишнее наружу: API возвращает весь объект из БД, включая password_hash и is_admin, надеясь, что клиент их «не отрендерит». Решение — отдавать только явно перечисленные поля (whitelist в сериализаторе). Вторая — лишнее внутрь: клиент дописывает в тело то, чего там быть не должно:
{
"name": "Иван",
"email": "[email protected]",
"role": "admin"
}
Если сервер делает «сохрани всё, что прислали», обычный пользователь сделает себя админом. Защита — принимать только разрешённый набор полей, а role и подобные — никогда из тела клиента.
Отдельного внимания заслуживает broken authentication — слабая проверка личности. Сюда относятся предсказуемые или короткие токены, отсутствие срока истечения, передача учётных данных в URL (они попадают в логи и историю браузера), приём слабых паролей и отсутствие защиты от перебора. Признак здоровой аутентификации — токены достаточной длины и случайности, обязательное истечение, защита эндпоинта логина rate-лимитом и единый, не раскрывающий деталей ответ на ошибку входа (не «нет такого пользователя» против «неверный пароль» — это подсказывает атакующему, какие логины существуют).
Валидация входных данных
Главный принцип: не доверять клиенту вообще. Любой вход проверяется на сервере: типы, диапазоны, длины, форматы, обязательность. Клиентская валидация — лишь удобство для пользователя, обойти её тривиально (тот же curl). Сервер обязан:
- проверять типы и форматы (email — это email,
age— целое 0..150); - отвергать неизвестные/лишние поля или игнорировать их;
- экранировать данные перед записью в БД (против инъекций) и перед выводом;
- на невалидный вход возвращать
400 Bad Requestс понятным описанием.
HTTPS обязателен
Весь трафик API должен идти по HTTPS (TLS). По обычному HTTP заголовки, тело и токены передаются открытым текстом — любой на пути (публичный Wi-Fi, провайдер) прочитает Authorization: Bearer <token> и угонит сессию. TLS шифрует канал и подтверждает подлинность сервера. На проде HTTP-эндпоинтов быть не должно: запросы по HTTP либо отклоняют, либо редиректят на HTTPS, а заголовок Strict-Transport-Security заставляет браузер всегда ходить только по HTTPS.
CORS: безопасные кросс-доменные запросы
Браузер по умолчанию запрещает скрипту с одного origin (схема+домен+порт) читать ответы API на другом origin — это Same-Origin Policy, защита от того, чтобы зловредный сайт дёргал ваш API от имени залогиненного пользователя. CORS (Cross-Origin Resource Sharing) — механизм, которым сервер явно разрешает конкретным origin обращаться к нему.
Для «непростых» запросов (методы вроде PUT/DELETE, кастомные заголовки) браузер сначала шлёт preflight — предварительный запрос OPTIONS, спрашивая разрешение:
Браузер Сервер API | OPTIONS /v1/orders | | Origin: https://app.example.com | | Access-Control-Request-Method: DELETE | |---------------------------------------->| |<----------------------------------------| | 204 No Content | | Access-Control-Allow-Origin: https://app.example.com | Access-Control-Allow-Methods: GET, POST, DELETE | Access-Control-Allow-Headers: Authorization, Content-Type | | | (preflight ok) DELETE /v1/orders/5 | |---------------------------------------->| |<------------- 200 OK --------------------|
Ключевой заголовок ответа — Access-Control-Allow-Origin: он перечисляет, какому origin браузер позволит прочитать ответ. Если в нём не ваш сайт — браузер заблокирует чтение, даже если сервер данные вернул.
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Как работает под капотом
CORS — это исключительно браузерная защита: проверку делает сам браузер, сравнивая Origin запроса с тем, что разрешил сервер в Access-Control-Allow-Origin. Сервер при запрете всё равно может вернуть данные — но браузер не отдаст их JavaScript. Поэтому CORS не защищает API от curl или серверных клиентов (у них нет Same-Origin Policy); он лишь регулирует, какие сайты в браузере могут читать ваш ответ. Access-Control-Max-Age кэширует результат preflight, чтобы браузер не слал OPTIONS перед каждым запросом. Распространённая опасная практика — Access-Control-Allow-Origin: * вместе с куками: браузер это запрещает, а для приватных API звёздочка вообще недопустима.
Частые ошибки
- Проверять токен, но не проверять владение объектом — это BOLA, главная дыра.
- Отдавать весь объект из БД, включая хэши и служебные флаги.
- Сохранять все присланные поля скопом — открывает mass assignment (
role: admin). - Полагаться на клиентскую валидацию — её обходят за секунду.
- Думать, что CORS защищает данные: он защищает только браузерных читателей, не
curl. - Ставить
Access-Control-Allow-Origin: *на приватный API. - Разрешать HTTP-эндпоинты в проде — токены утекают открытым текстом.
Итоги
- OWASP API Top 10 — карта главных угроз; BOLA, broken auth, excessive data exposure, mass assignment, отсутствие rate limiting в верхушке.
- BOLA лечится проверкой владения объектом при каждом доступе.
- Не доверяй клиенту: валидируй вход на сервере, ограничивай поля и на входе, и на выходе.
- HTTPS обязателен — иначе токены и данные идут открыто.
- CORS — браузерный механизм:
Access-Control-Allow-Originрешает, какой origin прочитает ответ; «непростые» запросы предваряет preflightOPTIONS. - CORS не заменяет авторизацию и не защищает от не-браузерных клиентов.