Отсутствие лимитов и инвентаризация
Разбираем, как API без лимитов превращается в инструмент перебора и отказа в обслуживании, и как забытые эндпоинты становятся дырой, — и как закрыть это лимитами и инвентаризацией.
Unrestricted Resource Consumption — у API нет ограничений на частоту и «вес» запросов, поэтому его можно перегрузить или использовать для перебора. Improper Inventory Management — организация не знает всех своих API: остаются теневые, тестовые и устаревшие эндпоинты без защиты.
Это две «операционные» категории OWASP API Top 10: они не про одну строку кода, а про то, как API живёт во времени и под нагрузкой. Изучаем на своём стенде и своих сервисах. Преднамеренная перегрузка чужого сервиса — это нарушение работы информационных систем (ст. 273/274 УК РФ), а массовый перебор данных — ст. 272.
Зачем это знать защитнику
Многие уязвимости из прошлых уроков «дозревают» именно из-за отсутствия лимитов: перебор id в BOLA, перебор OTP в аутентификации, выгрузка всей базы через чрезмерную выдачу — всё это становится практичным, когда можно слать тысячи запросов в секунду без последствий. А забытый старый эндпоинт обходит все ваши новые защиты, потому что про него никто не вспомнил. Лимиты и инвентаризация — это «гигиена», без которой остальные меры частично обесцениваются.
Без лимитов: перебор и отказ в обслуживании
Если эндпоинт можно вызывать без ограничения частоты, перебор становится дешёвым. Зеркальная проблема — «тяжёлые» запросы без ограничения объёма. Классический пример — список без пагинации:
@app.get("/api/products")
def list_products(_ = Depends(current_user)):
# отдаём ВСЁ разом — нет ни limit, ни offset
return db.products.find_all() # на больших данных съест память и время
Один запрос с миллионом строк нагружает базу, сериализацию и сеть; несколько таких параллельно кладут сервис. Особо коварен GraphQL: вложенные запросы могут запросить «заказы → товары → отзывы → авторы → их заказы…» и за один HTTP-запрос породить экспоненциальную нагрузку. И там, и там корень один — клиент управляет объёмом работы, а сервер не ставит потолок.
Теневые и устаревшие эндпоинты
Вторая беда — потеря учёта. Типичные находки на ревизии: /api/v1/... остался включён после выхода v2, но без новых проверок прав; /api/internal/... или /api/debug/... доступен снаружи; забытый стенд staging.api.example.com смотрит в боевую базу. Атакующий-исследователь ищет именно такие «двери», потому что старый код обычно слабее свежего: в нём ещё нет недавно добавленной авторизации, лимитов и логирования. Сюда же относятся документация и swagger-страница, оставленные открытыми в проде, и пути, про которые знает только один разработчик, ушедший из команды. Чем дольше живёт сервис, тем больше у него таких накопленных «хвостов», и каждый из них — это поверхность атаки, которую ваши новые меры просто не покрывают, потому что про неё не помнят.
Как это работает под капотом
API по умолчанию выполнит столько работы, сколько попросит клиент: вернёт сколько угодно строк, примет сколько угодно запросов, развернёт сколь угодно глубокий граф. Если потолок не задан явно, его определяет атакующий. Точно так же эндпоинт по умолчанию доступен, пока его явно не выключили: «выкатили и забыли» — это рабочая, но небезопасная стратегия. Защита в обоих случаях — сделать осознанным то, что иначе происходит само: задать лимиты и вести точный список того, что вообще опубликовано.
Как защититься
1. Rate limiting на чувствительные эндпоинты. Ограничивайте частоту запросов на ключ/IP, особенно на вход, OTP и выборки по id. Концептуально лимит выглядит так:
правило: не более 5 попыток входа в минуту на учётную запись
порог превышен -> ответ 429 Too Many Requests + временная блокировка
где ставить: обратный прокси / API-шлюз / middleware приложения
Команды-иллюстрации для нагрузочной проверки своего стенда (только лаборатория):
# нагрузочный тест собственного API в локальной ВМ
ab -n 1000 -c 50 http://localhost:8000/api/products
# (ApacheBench шлёт 1000 запросов, 50 параллельно)
2. Обязательная пагинация и потолки. Сделайте пагинацию неотключаемой и ограничьте максимум на странице, чтобы клиент не мог запросить «всё»:
@app.get("/api/products")
def list_products(limit: int = 20, offset: int = 0, _ = Depends(current_user)):
limit = min(limit, 100) # жёсткий потолок страницы
return db.products.find(limit=limit, offset=offset)
Для GraphQL дополнительно ограничивайте глубину и сложность запроса, ставьте таймаут и максимальный размер тела запроса. Везде задавайте разумный timeout, чтобы один запрос не держал ресурсы бесконечно.
3. Инвентаризация API. Ведите актуальный реестр эндпоинтов и их версий: что опубликовано, какая версия, кто владелец, есть ли на ней авторизация и лимиты. Держите единый источник правды через OpenAPI-спецификацию, генерируемую из кода, и сверяйте её с тем, что реально открыто наружу. Старые версии выводите из эксплуатации по расписанию (deprecate → отключить), а не «пусть пока повисит». Не выставляйте наружу internal/debug/staging.
4. Обнаружение. Следите за всплесками 429 и аномальным трафиком на отдельные эндпоинты — это и сигнал атаки, и проверка, что лимиты работают. Периодически сканируйте свой внешний периметр на «лишние» открытые пути и поддомены, чтобы находить теневые API раньше атакующего.
Итоги
- Без лимитов API сам становится инструментом перебора и отказа в обслуживании: потолок, который не задал ты, задаёт атакующий.
- Ставьте rate limiting (особенно на вход, OTP, выборки по id) и неотключаемую пагинацию с жёстким максимумом страницы; для GraphQL — лимиты глубины и сложности.
- Теневые/устаревшие эндпоинты (
/v1,internal,debug, забытый staging) обходят новые защиты, потому что старый код слабее. - Ведите инвентаризацию: реестр и OpenAPI как источник правды, плановый вывод старых версий, никаких внутренних путей наружу.
- Мониторьте 429 и аномалии, сканируйте свой периметр. Практика — только на стенде и своих системах (ст. 272/273/274 УК РФ).