Ограничения размера запросов: ужесточите разбор тела, чтобы предотвратить DoS
Установите лимиты размера запросов и более безопасные правила парсинга тела, чтобы избежать всплесков памяти, замедлений и DoS из‑за слишком больших или некорректных запросов.

Почему большие запросы становятся реальной проблемой
Большие запросы кажутся безобидными, пока не выводят приложение из строя. Один крупный загруз, огромный JSON или запрос, застрявший в цикле повторов, может съесть память и CPU, замедлить всех остальных и иногда привести к падению сервера.
Пара простых терминов поможет:
- Payload: данные, которые отправляет клиент.
- Body: место в HTTP‑запросе, где обычно хранится payload (например, JSON или файл).
- Парсер: код или библиотека, которая читает body и преобразует его во что‑то, что использует приложение — объект, строку или сохранённый файл.
Риск исходит не только от «хакеров». Многие инциденты случаются по ошибке: мобильный баг отправляет 50 MB, фронтенд кодирует изображение в base64 внутри JSON, или интеграция продолжает добавлять поля, пока тело не станет огромным. Результат может выглядеть как DoS, даже если никто не хотел навредить.
Конечно, ту же уязвимость легко использовать целенаправленно. Если сервер принимает неограниченные тела, атакатор может отправлять очень большие запросы (или много средних) и заставить парсер сделать дорогую работу. Это может лишить приложение памяти, заполнить диск и блокировать легитимный трафик.
Ограничения размера запросов важны, но это не «настроил и забыл». Слишком маленькие лимиты ломают реальных пользователей (особенно при загрузках). Слишком большие — всё равно допускают всплески памяти. Цель — безопасный дефолт и явные исключения для тех немногих эндпойнтов, которые действительно нуждаются в больших телах.
Что может пойти не так, если вы принимаете большие полезные нагрузки
Принимать «всё, что пришлёт клиент» — быстрый путь сделать один эндпойнт причиной аутейджа. Без лимитов один большой POST может толкнуть приложение в высокий расход памяти, долгие ответы и каскадные отказы.
Первым ударом обычно идёт память. Многие фреймворки буферизуют всё тело до запуска вашего хэндлера. Большой JSON или multipart‑загрузка может копироваться несколько раз при буферизации, декодировании и валидации. Это умножает затраты памяти, и не нужно много параллельных запросов, чтобы исчерпать контейнер или VM.
Дальше — CPU. Парсинг больших JSON‑ов дорогой, а сильно вложенные объекты только усугубляют ситуацию. Даже «валидная» полезная нагрузка может занимать секунды обработки, отнимая CPU у реальной работы. Некоторые парсеры делают дополнительную работу на больших входах (приведение типов, валидация), что повышает стоимость каждого запроса.
Большие тела также занимают воркеры. Медленная загрузка держит соединение открытым, удерживая поток или цикл событий и доводя других пользователей до тайм‑аутов. Под нагрузкой ретраи накапливаются и усиливают ущерб.
Загрузки могут тихо навредить и диску. Если временные файлы попадают не туда, где нужно, или не удаляются, всплеск больших запросов может заполнить диск и вывести из строя несвязанные части приложения.
Команды часто недооценивают «вторичные» издержки: всплески трафика и счета в облаке (особенно при повторах клиентов), замедление фоновых очередей из‑за нагрузки на CPU и память, шумные логи и слепые зоны, потому что часть ошибок возникает ещё до запуска кода приложения. Ещё одна частая ошибка — делать проверку аутентификации после парсинга тела, что делает злоупотребление дешевле.
Реалистичный сценарий: эндпойнт регистрации принимает JSON с полем "profile". Один багнутый клиент присылает 50 MB. Сервер буферизует, парсит и зависает. Ещё несколько таких параллельно — и сервис становится недоступен.
Где нужно вводить лимиты, чтобы они реально работали
Самые надёжные лимиты применяются на нескольких уровнях. Если вы зададите только внутренний лимит в приложении, сервер всё равно может потратить время и память на чтение огромного тела до того, как ваш код его отвергнет. Если вы ставите лимит только на краю, всё равно полезно иметь более жёсткие правила для отдельных эндпойнтов.
1) Край (edge): остановите большие запросы до приложения
Первая защита — уровень, который получает трафик первым: CDN, балансировщик, reverse proxy или API‑gateway. Здесь вы можете отклонить большое тело раннее, вернуть HTTP 413 и избежать нагрузки на воркеры приложения. Это также помогает против медленных загрузок, цель которых — удерживать соединения открытыми.
Держите крайний лимит строгим для общего трафика. Если нужны большие загрузки, обрабатывайте их через отдельный путь или сервис, а не поднимайте лимит для всего.
2) Уровень приложения: лимиты по эндпойнтам и безопасные дефолты
Внутри приложения снова вводите лимиты, чтобы у каждого эндпойнта был подходящий потолок. Эндпойнт логина должен принимать маленький JSON. Эндпойнт фото профиля — больше.
Практичный паттерн — небольшой глобальный максимальный размер для большинства API‑маршрутов и пер‑роут ограничения для исключений. Публичные маршруты должны быть строже, чем аутентифицированные. Отклоняйте как можно раньше (проверяя Content-Length, если он есть) и используйте таймауты на чтение тела, а не только на обработку.
Загрузки требуют отдельной обработки. Трактуйте файловые загрузки иначе, чем обычные JSON‑API: они могут вызвать большие всплески памяти, если парсер буферизует всё. Предпочитайте потоковую или покадровую обработку, пишите на диск или в объектное хранилище и валидируйте тип и размер файла до дорогой работы.
Как выбрать безопасные лимиты, не сломав пользователей
Начните с инвентаризации всех эндпойнтов, принимающих тело. Лимиты безопасны только тогда, когда они соответствуют реальному использованию. Быстрее всего этого добиться, если посмотреть, что вы реально принимаете, а не что вы думаете.
Сгруппируйте эндпойнты по ожидаемому типу содержимого. JSON‑API обычно требуют самых маленьких лимитов. Формы — посередине. Загрузки файлов — большие лимиты. Webhooks иногда удивляют большими всплесками.
Практическое начало — жёсткие дефолты и повышение лимитов только при явной необходимости. Например:
- JSON API: 16 KB — 256 KB
- Формы (без файлов): 64 KB — 512 KB
- Загрузки файлов: 5 MB — 25 MB (только на конкретных маршрутах)
- Webhooks: 256 KB — 2 MB (в зависимости от документации провайдера и логов)
Эти числа не универсальны, важен подход: большинство маршрутов должно быть маленькими, и лишь немногие — большими.
При выборе лимита помните, что вы ограничиваете не только «пользовательские данные». Заголовки, куки и multipart‑границы добавляют оверхед. Base64 увеличивает объём примерно на треть. 2 MB‑изображение, упакованное в JSON, может стать почти 2.7 MB до парсинга.
Планируйте исключения явно. Если одному эндпойнту реально нужен 25 MB, дайте 25 MB только ему и держите дефолт низким. Зафиксируйте, кто пользуется исключением, что отправляет и какой разумный верхний предел.
Признак проблемы — один универсальный эндпойнт (например, “/api/save”), который принимает всё. Разделение на маленький JSON‑эндпойнт и отдельный путь для загрузок часто решает внезапные всплески памяти без ущерба для пользователей.
Правила парсинга тела, которые уменьшают всплески памяти и CPU
Самый быстрый путь вызвать пиковые нагрузки — позволить приложению догадываться, что содержит тело, и парсить его автоматически. Ужесточение парсинга означает быть строгим: принимать только ожидаемое и парсить только тогда, когда это действительно нужно.
Начните со белого списка Content‑Type для каждого эндпойнта. Если эндпойнт ожидает JSON — принимайте только application/json (и точные варианты, которые вы поддерживаете). Если Content‑Type отсутствует или неизвестен, отклоняйте рано. Это предотвращает случайный парсинг огромного текста, странных кодировок или входов, которые заставляют парсер работать сверх меры.
Ограничьте парсинг JSON
Если ваш фреймворк поддерживает, ограничьте не только размер, но и сложность JSON. Маленький запрос может быть дорогим, если он глубоко вложен или содержит тысячи ключей.
Полезные дефолты:
- Максимальная глубина JSON (пример: 20–50 уровней)
- Максимальное количество полей (пример: 1 000–10 000 ключей)
- Максимальная длина строки в поле (пример: 10 KB — 100 KB)
- Строгая обработка UTF‑8 (отклонять некорректные последовательности)
- Быстрый отказ при дублирующихся ключах (если поддерживается)
Далее, отключайте автоматический парсинг на тех маршрутах, где он не нужен. Многие приложения парсят JSON глобально для каждого запроса, включая health check, webhook verification и простые GET — это лишняя работа и лёгкая цель.
Для загрузок предпочитайте потоковую обработку (обработка чанками), а не чтение всего файла в память перед записью на диск или в объектное хранилище. Комбинируйте стриминг с лимитами, чтобы один запрос не мог заполнить RAM.
Пример: эндпойнт регистрации ожидает маленький JSON. Если сервер принимает любой Content‑Type и парсит автоматически, атакатор может отправить многомегабайтный payload со сложной структурой, который загрузит CPU. Жёсткие правила по Content‑Type вместе с ограничением глубины и числа полей превратят это в быстрый отказ вместо простоя.
Шаг за шагом: добавляем лимиты и ужесточаем парсинг
Большинство инцидентов с большими запросами попадают в легко достижимые эндпойнты: публичные формы, неаутентифицированные API и webhooks. Начните с списка всех маршрутов, принимающих тело, затем пометьте публичные, вызываемые третьими сторонами и те, что принимают файлы.
Практический чеклист
Начните с края (балансировщик, reverse proxy, CDN или API‑gateway). Краевые контролы — первая линия защиты, потому что они блокируют запрос до того, как приложение потратит память на парсинг.
- Определите эндпойнты высокого риска (публичные, без аутентификации, webhooks, загрузки).
- Установите крайние лимиты: максимальный размер тела, максимальный размер заголовков и короткий таймаут чтения.
- Добавляйте исключения по пути только с обоснованием (например, маршрут для загрузок).
- Убедитесь, что край возвращает HTTP 413 для превышающих лимит тел.
- Проверьте, что лимит действительно работает (некоторые стэки буферизуют до отклонения).
Затем ужесточите внутри приложения. Лимиты внутри защищают, если трафик обходит край (внутренние вызовы, неправильная конфигурация) и дают более тонкие правила, чем один глобальный потолок.
- Примените лимиты размера запроса по маршрутам и типам контента (JSON vs multipart).
- Задайте дефолты парсеров: макс‑размер JSON, максимальная глубина и строгий парсинг (отклонять некорректный JSON; отклонять дублирующие ключи, если поддерживается).
- Избегайте парсинга в память, когда можно: стримьте загрузки и валидируйте тип и размер на раннем этапе.
- Тестируйте нормальные и намеренно завышенные запросы, включая сжатые тела, если вы их принимаете.
- Наблюдайте логи на предмет 413 и таймаутов чтения в течение недели, затем корректируйте только при реальной необходимости.
Реальный кейс: webhook принимал JSON без лимита. Один большой payload спайкнул память и рестартовал сервис. Маленький per‑route JSON‑лимит на webhook (при сохранении больших лимитов только на авторизованных upload‑маршрутах) предотвратил проблему, не мешая нормальному трафику.
Обработка ошибок: безопасно и удобно для пользователя
Когда вы вводите лимиты, важно, что видит клиент при переборе. Хорошая обработка ошибок останавливает атаку или ошибку быстро, но подсказывает нормальному пользователю, как исправить запрос.
Используйте соответствующие статусы. Полезная нагрузка, превышающая лимит, должна вернуть 413 Payload Too Large. Тело, которое вы не поддерживаете (например, XML на JSON‑эндпойнте) — 415 Unsupported Media Type. При некорректном запросе подойдёт 400 Bad Request. Соответствие ошибки помогает клиентам избежать слепых повторов.
Держите сообщения короткими и практичными: “Файл слишком большой. Макс 10 MB.” или “Поддерживается только application/json.” Не возвращайте тело запроса или заголовки в ответ — так снижается шанс утечки секретов.
В логах достаточно контекста для отладки без хранения отклонённого payload. Хороший компромисс — логировать эндпойнт и метод, код ответа (413, 415), наблюдаемый Content‑Length (если есть) и сконфигурированный лимит, Content‑Type, request id и user/account id (если известны).
Решите, где отказывать быстро на уровне каждого эндпойнта. Для публичных upload‑эндпойнтов и маршрутов аутентификации отказ на краю экономит работу приложения. Для маршрутов с маршрутно‑специфическими правилами приложение может решать, но всё равно должно отвергать до парсинга полного тела.
Распространённые ошибки, ведущие к простоям или лёгкому DoS
Большинство инцидентов возникают не из‑за полного отсутствия лимитов, а из‑за того, что лимиты непоследовательны, обходятся или применяются слишком поздно.
Один частый паттерн — единый глобальный потолок и уверенность, что этого достаточно. Затем загрузочный маршрут, webhook или исключение reverse proxy тихо позволяют гораздо большие тела. Атакующийу не нужно бить по основному API — достаточно одного слабого эндпойнта.
Повторяющиеся ошибки:
- Ограничение JSON, но забыли про загрузки и webhooks (или сделали их «неограниченными»).
- Повысили лимит «на время», чтобы разблокировать клиента, а потом не вернули назад.
- Парсите тело, а проверяете размер после — к тому моменту вы уже заплатили за память и CPU.
- Принимаете любой Content‑Type и позволяете библиотекам догадываться, что парсить, что может запустить медленные пути парсинга или неожиданную декомпрессию.
- Разрешаете файлы в base64 внутри JSON без строгих ограничений — «10 MB файл» раздувается в памяти.
Ещё один распространённый промах — заблокировать реальных пользователей, потому что лимиты не тестировались. Мобильные клиенты могут отправлять большие заголовки. Веб‑хуки партнёров могут включать громоздкие метаданные. Если вы угадаете число и выпустите код, вам об этом скажут в самый неподходящий час.
Лучший подход — протестировать лимиты с реальными клиентами перед жёстким включением. Соберите пару репрезентативных payloads (нормальный, близкий к лимиту, явный перебор), задайте лимит с запасом и делайте сообщения об ошибках понятными.
Быстрые проверки, которые можно сделать за 15 минут
Не нужно большое security‑проектирование, чтобы быстро снизить риск. Простой проход по настройкам лимитов и парсеров может предотвратить внезапные всплески памяти и лёгкие DoS‑атаки.
15‑минутная проверка
Начните с края (балансировщик, CDN, reverse proxy). Если он принимает огромные тела, ваше приложение может никогда не получить шанс защититься. Затем проверьте маршруты приложения.
- Убедитесь, что на краю есть жёсткий лимит размера тела и он действительно применяется (попробуйте запрос, превышающий лимит).
- Выберите 3–5 эндпойнтов с телами и запишите их предполагаемые максимумы.
- Добавьте белый список Content‑Type для эндпойнтов с телом.
- Проверьте настройки JSON‑парсера на предмет макс‑размера и ограничьте глубину/сложность, если фреймворк поддерживает.
- Для загрузок не буферизуйте весь файл в памяти — используйте стриминг или выделенный поток загрузки.
5‑минутный тест отказа
Проверьте, что приложение отказывает безопасно и предсказуемо. Нужен понятный ответ, видимый в логах и мониторинге.
Проверьте:
- HTTP 413 при слишком большом payload (и аккуратное закрытие соединения).
- HTTP 415 при неподдерживаемом Content‑Type.
- Таймауты, обрывающие медленные бесконечные загрузки.
- Один шумный IP не должен запускать повторные дорогостоящие парсинги.
Пример: остановка реального всплеска памяти на одном эндпойнте
Публичный эндпойнт регистрации выглядел нормально в тестах, затем стал таймаутить при всплеске трафика. CPU рос, память прыгала и приложение рестартовало. Казалось, что «слишком много пользователей», но логи показали другое: небольшое число запросов занимали намного больше времени.
Причина была в форме payload, а не в количестве. Атакующие и багнутые клиенты посылали большие JSON‑тела и глубоко вложенные объекты. Даже когда валидация в итоге отклоняла запрос, сервер уже потратил время и память на чтение и парсинг. Пара таких запросов могла загнать процесс в треш сборки мусора и затем OOM.
Решение было простым: лимиты размера запроса и строгие правила парсинга, применённые до бизнес‑логики.
Что изменилось
Мы ужесточили эндпойнт регистрации так, чтобы он принимал только то, что реально нужно:
- Небольшой максимальный размер тела для JSON
- Максимальная глубина вложенности JSON
- Строгая проверка Content‑Type (только JSON)
- Короткие таймауты на чтение тела
- Чёткие HTTP 413‑ответы с коротким сообщением
После этого тот же всплеск трафика больше не вызывал рестартов. Память оставалась стабильной, потому что гигантские тела не попадали в JSON‑парсер, а глубоко вложенные структуры отклонялись быстро. Логи стали чище: вместо длинных трасс были короткие записи вроде “Payload too large” или “JSON too deep.”
Для реальных пользователей почти ничего не поменялось. Обычные регистрации по‑прежнему проходили. Разница была в том, что сломанные клиенты получали быстрый понятный отказ вместо бесконечного ожидания и общего таймаута.
Следующие шаги и когда просить помощи
Прежде чем менять что‑то, сделайте быстрый снимок того, что вы защищаете. Это поможет не сломать реальных пользователей и одновременно закрыть простые DoS‑пути.
Соберите:
- Список эндпойнтов, принимающих тела (JSON, формы, загрузки)
- Текущие лимиты размера запросов (по маршруту и на прокси/сервере)
- Недавние логи с 413, таймаутами, всплесками памяти и медленными запросами
- Заметки о том, кто использует каждый эндпойнт и типичные размеры payload
- Любые фоновые задания, которые отправляют большие внутренние payloads
Далее внедряйте изменения в безопасном порядке: сначала публичные маршруты, затем webhook‑приёмники, затем любые эндпойнты, которые парсят непроверенный JSON. Держите лимиты жёсткими на этих путях и ослабляйте их только при явной необходимости и безопасном пути парсинга.
После деплоя следите, чтобы лимиты не раздувались со временем. Сохраните несколько репрезентативных запросов (обычный, близкий к лимиту, явный перебор) и прогоняйте их после каждого релиза — убедитесь, что отказ быстрый, 413 возвращается корректно и память/CPU стабильны.
Если вы унаследовали AI‑сгенерированный код, стоит провести целевой аудит аудита на рискованные дефолты: неограниченные парсеры тела, эндпойнты загрузки без лимитов и слабая валидация входа. FixMyMess (fixmymess.ai) специализируется на диагностике и починке AI‑сгенерированных приложений: от ужесточения лимитов запросов и исправления путей парсинга, которые буферизуют в памяти, до закрепления сопутствующих проблем безопасности. Они также предлагают бесплатный аудит, чтобы выявить проблемы до внедрения изменений.
Часто задаваемые вопросы
Почему большие запросы так легко вызывают простои?
Безопасный подход — задать небольшой глобальный предел для большинства JSON‑эндпойнтов и разрешать большие размеры только для тех маршрутов, которым это действительно нужно (например, загрузки). Так один случайный или вредоносный запрос не сможет потребить достаточно памяти и CPU, чтобы замедлить или упасть сервер.
Как выбрать лимит размера запроса, чтобы не сломать реальных пользователей?
Начните с того, что посмотрите, что реально отправляют ваши клиенты, и выберите предел, который покрывает обычное и «худший нормальный» использование с небольшим запасом. Держите большинство JSON‑маршрутов строгими, а загрузки и webhooks рассматривайте как особые случаи с отдельными лимитами, чтобы не поднимать потолок для всего приложения.
Нужно ли ставить лимиты и на краю, и внутри приложения?
Чем раньше вы отклоняете запрос, тем дешевле это обходится системе. Краевые лимиты (CDN, балансировщик, reverse proxy) останавливают большие тела до того, как они займут воркеры и память, а лимиты в приложении позволяют задавать правила по маршрутам и защищают внутренние вызовы, которые могут обходить край.
Что должно возвращать API, если полезная нагрузка слишком велика?
Возвращайте 413 Payload Too Large, когда тело превышает лимит, и делайте сообщение коротким и понятным, например: “Файл слишком большой. Макс 10 MB.” Не включайте обратно содержимое запроса или заголовки — так вы уменьшаете риск утечки секретов.
Почему проверять размер после парсинга — распространённая ошибка?
Потому что во многих стеках тело буферизуется и парсится до того, как ваш хэндлер начнёт проверку. Отложенный откат всё равно заставит сервер потратить память и CPU, что при конкурентных запросах может привести к тайм‑аутагам или рестартам.
Насколько строго относиться к Content‑Type на эндпойнтах с телом?
Разрешайте на каждом эндпойнте только те значения Content‑Type, которые вы ожидаете. Это предотвращает попытки сервера «угадать» формат и снижает риск случайного запуска тяжёлых путей парсинга или декомпрессии.
Какие лимиты парсинга JSON помогают избежать пиков CPU?
Одного размера недостаточно: маленькое, но глубоко вложенное JSON‑структура может сильно нагружать CPU. Если ваш фреймворк поддерживает, добавьте пределы на глубину JSON, общее количество полей и максимальную длину строк, чтобы быстро отвергать специально тяжёлые входы.
Почему загружать файлы в JSON в виде base64 — плохая идея?
Base64 увеличивает объём данных примерно на треть и часто заставляет сервер держать большие строки в памяти при парсинге и валидации. Лучше выделить отдельный поток загрузки, который обрабатывает файл потоково и применяет лимиты по размеру файла, вместо встраивания файлов в JSON.
Что стоит логировать при отклонении больших запросов?
Логируйте эндпойнт, код ответа, сконфигурированный лимит, наблюдаемый Content‑Length (если есть), Content‑Type и идентификатор запроса, но не тело. Это даёт достаточно контекста для отладки и настройки лимитов без хранения больших или чувствительных данных.
Когда стоит попросить помощь в ужесточении лимитов и парсеров?
Если у вас унаследован код, сгенерированный AI, часто лимиты и парсеры оставлены на небезопасных значениях или применяются непоследовательно по маршрутам. FixMyMess может провести бесплатный аудит, найти рискованные места в разборе тела, отсутствующие лимиты и проблемы с загрузками, а затем быстро внедрить проверенные исправления — обычно за 48–72 часа.