Ограничение перебора в веб-приложении: типичные ошибки
Один лимит по IP не остановит перебор паролей: атакующий сменит адреса, а общий NAT заблокирует реальных пользователей. Разбираем, где ставить ограничения, как сочетать IP и аккаунт и что проверить в проде.

Ограничение перебора часто сводят к одной строке на Nginx: не больше пяти запросов в минуту с одного IP. Это защищает только от простейшего сценария. Credential stuffing* распределяет попытки по тысячам адресов, а жёсткий лимит по IP способен одновременно заблокировать офис, мобильного оператора или всю корпоративную сеть. Ниже — практическая схема для веб-приложения с формой входа: что ограничивать, где хранить счётчики и какие ошибки ломают защиту.
Сначала определите, что именно вы ограничиваете
У запроса на вход есть несколько независимых признаков: IP-адрес, идентификатор аккаунта, устройство, сессия и результат проверки пароля. Один счётчик не учитывает их все. Если считать только IP, атакующий меняет адреса. Если считать только логин, злоумышленник может заблокировать чужую учётную запись множеством неверных попыток.
Для формы входа обычно нужны как минимум два уровня:
- мягкий лимит по IP — сдерживает поток запросов к endpoint и защищает сервер от дешёвого массового перебора;
- лимит по аккаунту — реагирует на серию неудачных входов для одного логина независимо от IP;
- общий лимит на endpoint — не даёт распределённой атаке занять все воркеры приложения;
- отдельные правила для восстановления пароля и отправки кодов — эти операции нельзя автоматически считать обычным входом.
Не смешивайте ограничение частоты и блокировку учётной записи. Rate limit замедляет поток запросов, а lockout временно запрещает действие для конкретного аккаунта. У них разные последствия: первый повышает стоимость атаки, второй может превратиться в инструмент отказа в обслуживании.
* Credential stuffing — автоматическая проверка украденных пар логин–пароль на другом сервисе. В отличие от обычного перебора, атакующий часто использует небольшое число попыток на каждый аккаунт.
Хорошее ограничение должно замедлять атакующего, но не давать ему простой способ заблокировать чужой аккаунт.
Три уровня защиты и цена каждого
1. Ограничение на reverse proxy
Прокси отбрасывает слишком частые запросы до того, как они попадут в приложение. Это дешёвый первый рубеж: он экономит соединения, CPU и обращения к базе. Но Nginx видит только тот адрес, который вы ему передали, а лимит по IP плохо работает против распределённой атаки и общих сетей.
Пример для Nginx 1.26.x. Значения ниже демонстрационные: их нельзя переносить в прод без замеров по обычному трафику.
http {
limit_req_zone $binary_remote_addr zone=login_by_ip:10m rate=5r/m;
server {
location = /login {
limit_req zone=login_by_ip burst=5 nodelay;
limit_req_status 429;
proxy_pass http://application;
}
}
}
Здесь для каждой пары «IP — зона» задаётся скорость пять запросов в минуту, а burst=5 допускает короткую очередь превышения. Флаг nodelay не растягивает такую очередь по времени, а пропускает её сразу, пока не исчерпан запас. После изменения конфигурации проверьте синтаксис командой nginx -t, затем выполните штатную перезагрузку Nginx и убедитесь, что лишние запросы получают HTTP 429.
Не добавляйте вслепую X-Forwarded-For или X-Real-IP в качестве доверенного адреса. Эти заголовки безопасны только тогда, когда их устанавливает ваш доверенный балансировщик, а Nginx принимает запросы из известных сетей. Иначе клиент сможет подставлять новый IP в каждом запросе.
2. Ограничение внутри приложения
Приложение знает логин, результат проверки, тип операции и контекст пользователя. Поэтому именно здесь удобно вводить задержку или временное ограничение для конкретного аккаунта. Счётчик неудач должен увеличиваться после неверной проверки, а успешный вход — сбрасывать или пересматривать его по понятному правилу.
Не сообщайте, существует ли аккаунт. Ответы «пользователь не найден» и «неверный пароль» должны иметь одинаковый внешний формат и сопоставимое время выполнения. Иначе перебор превращается сначала в перечисление зарегистрированных адресов, а затем — в подбор паролей только для найденных аккаунтов.
Цена такого подхода — сложность. При нескольких экземплярах приложения счётчик в локальной памяти будет разным на каждом экземпляре. Пользователь отправит запрос на один узел, затем на другой и обойдёт ограничение. Для общего состояния нужен внешний компонент, например Redis 7.x, либо механизм, который уже предоставляет ваша платформа.
3. Распределённый лимит
Общий счётчик в Redis или другом отказоустойчивом хранилище позволяет применять одно правило ко всем экземплярам приложения. Ключи стоит разделять по назначению: отдельно IP, аккаунт и операция. Для чувствительных операций используйте короткое время жизни ключа, чтобы устаревшие записи не копились бесконечно.
У распределённого варианта есть собственная цена: дополнительный сетевой запрос, зависимость от доступности хранилища и необходимость решить, что делать при его сбое. Для входа режим fail-open пропустит запросы без лимита и снизит защиту. Режим fail-closed сохранит защиту, но может положить авторизацию при проблеме с Redis. Выбор нужно зафиксировать заранее и проверить аварийным тестом.
| Подход | Что хорошо защищает | Основная слабость | Когда выбирать |
|---|---|---|---|
| Nginx по IP | Всплеск запросов и простую атаку с одного адреса | Не видит аккаунт, плохо работает против распределённого трафика | Как первый рубеж перед приложением |
| Лимит в приложении | Конкретный аккаунт и результат проверки пароля | Локальная память не подходит для нескольких узлов | Для бизнес-правил и сообщений пользователю |
| Общее хранилище | Распределённую атаку и несколько экземпляров сервиса | Добавляет зависимость и сценарии отказа | Для горизонтально масштабируемого продакшена |
Типичные ошибки, которые снижают защиту
Лимит только по IP
Это самая частая ошибка. Мобильные сети, офисы и провайдерский NAT объединяют множество пользователей под одним публичным адресом. Слишком низкий порог создаст ложные срабатывания, а высокий порог почти не повлияет на распределённую атаку.
Используйте IP-ограничение как сетевой предохранитель, но не как единственный контроль. Порог выбирайте по журналам успешных и неудачных входов: измерьте пики, долю общих адресов и частоту повторных попыток. Универсального безопасного числа нет.
Блокировка аккаунта после нескольких ошибок
Постоянная блокировка после фиксированного числа ошибок удобна администратору, но опасна для доступности сервиса. Атакующему достаточно знать адрес электронной почты, чтобы регулярно блокировать чужую учётную запись.
Безопаснее применять нарастающую задержку, временное ограничение и дополнительную проверку риска. Если бизнес всё же требует блокировки, у пользователя должен быть рабочий канал восстановления, а служба поддержки — способ отличить атаку от обычной ошибки.
Счётчик увеличивается не на том этапе
Если увеличивать его только после успешного поиска аккаунта, злоумышленник легко перебирает несуществующие логины. Если увеличивать до разбора запроса, повреждённые или явно некорректные запросы могут занимать тот же лимит, что и реальные попытки.
Разделите правила для синтаксически неверного запроса, неизвестного аккаунта и неверного пароля. Логи могут различать эти события, но внешний ответ клиенту должен оставаться нейтральным.
Проверка CAPTCHA на каждый запрос
CAPTCHA не заменяет ограничение частоты. Она добавляет трение реальному пользователю, плохо работает с доступностью и может быть автоматизирована. Показывайте дополнительную проверку только после сигнала риска: серии неудач, необычной географии, нового устройства или резкого изменения поведения.
Один лимит для всех операций
Вход, восстановление пароля, отправка одноразового кода и смена пароля имеют разные последствия и стоимость. Общий счётчик позволяет дешёвой операции исчерпать бюджет более важной. Для каждой операции задайте отдельный ключ, окно времени и порядок уведомлений.
Что проверять в логах и метриках
До настройки порогов соберите базовую линию хотя бы за период, в котором видны обычные пики. Смотрите не только на количество ответов 429. Нужны:
- число попыток входа на IP и на аккаунт;
- доля неудачных проверок и распределение по кодам ответа;
- количество уникальных IP для одного аккаунта;
- количество аккаунтов, к которым обращается один IP;
- задержка ответа и ошибки внешнего хранилища лимитов;
- доля легитимных пользователей, попавших под ограничение.
Не записывайте в логи пароли, токены, коды из SMS и полные значения секретных заголовков. Идентификатор аккаунта тоже может быть персональными данными: определите срок хранения и доступ к таким журналам по правилам вашей системы.
Где подход ломается
Ограничение запросов не спасает от уже скомпрометированных аккаунтов, фишинга и повторного использования паролей. Оно также не заменяет многофакторную аутентификацию, защиту сессий и уведомления о подозрительном входе.
Отдельная граница — прокси и CDN. Если приложение получает адрес балансировщика, а не клиента, лимит по IP может начать душить весь трафик. Если вы доверяете заголовку с адресом клиента без контроля цепочки прокси, лимит можно обойти подменой заголовка. Перед включением правила нарисуйте путь запроса: клиент, CDN, балансировщик, Nginx, приложение.
Проверьте и гонки при обновлении счётчика. Два параллельных запроса не должны оба прочитать старое значение, пройти проверку и записать одинаковый новый результат. Для общего хранилища используйте атомарную операцию, которую поддерживает выбранный клиент или сервер; точное имя команды зависит от реализации и версии.
Что делать дальше
Начните с карты всех чувствительных endpoint: вход, восстановление, отправка кода, смена пароля и административная авторизация. Затем включите умеренный лимит на Nginx 1.26.x, добавьте независимое правило по аккаунту в приложении и проверьте работу на нескольких экземплярах сервиса. После этого проведите нагрузочный тест с контролируемыми тестовыми аккаунтами, измерьте ложные блокировки и отдельно отключите Redis или другой компонент общего состояния.
В продакшене первым показателем для наблюдения будет не только число 429, а отношение ограниченных запросов к успешным входам и количество уникальных IP на аккаунт. Если значение резко меняется, ищите не только новый перебор, но и ошибку в доверии к proxy-заголовкам, клиентском ретрае или конфигурации балансировщика.
Логотип и фирменный стиль: Три концепции разного характера, палитра с обоснованием, проверка на читаемость в малом размере и в один цвет.
Спросить за 60 ₽Источники: Nginx documentation: ngx_http_limit_req_module, OWASP Authentication Cheat Sheet
Частые вопросы
Достаточно ли поставить limit_req в Nginx?
Какой лимит запросов выбрать для формы входа?
Можно ли блокировать аккаунт после пяти неверных паролей?
Что будет, если приложение работает на нескольких серверах?
Почему лимит по IP блокирует пользователей офиса?
Что делать, если после настройки реальные пользователи получают 429?
Материал носит информационный характер и подготовлен редакцией «Агентуры». Он не является офертой, рекламой или индивидуальной консультацией. Упомянутые продукты, компании и торговые знаки принадлежат их правообладателям. Перед принятием решений, влекущих юридические или финансовые последствия, обратитесь к профильному специалисту.