Перейти к основному содержанию
Блог

Веб

Защита входа: brute-force, rate limit, OTP и безопасность сессии

Страница входа — это дверь в систему. Практические проверки: ограничение подбора паролей, OTP-коды, сброс пароля и правильное управление сессией.

· 8 мин. чтения

Фото: Santeri Viinamäki, CC BY-SA 4.0 · Wikimedia Commons

Страница входа — самая проверяемая часть любого портала или кабинета. И экспертиза, и реальный атакующий смотрят сюда в первую очередь: можно ли подобрать пароль, узнать, существует ли пользователь, обойти OTP-код, закрывается ли сессия после выхода. Ниже разберём сценарий входа по шагам.

1. Brute-force и rate limit

Форма входа без ограничений открыта для автоматического подбора пароля. Защита должна быть многослойной:

  • Ограничение по аккаунту + IP. Ограничения только по IP мало — атакующий меняет адреса. Ограничение только по аккаунту приводит к блокировке настоящего пользователя. Совмещайте оба.
  • Общее ограничение. Ограничивайте и попытки с одного IP по многим аккаунтам (password spraying).
  • Прогрессивная задержка или CAPTCHA. После нескольких неудачных попыток — CAPTCHA или увеличение времени ожидания.

Пример на Laravel:

RateLimiter::for('login', function (Request $request) {
    $login = Str::lower((string) $request->input('phone'));
    return [
        Limit::perMinute(5)->by($login.'|'.$request->ip()),
        Limit::perMinute(30)->by($request->ip()),
    ];
});

Проверить просто: отправьте неверный пароль 10–15 раз подряд и посмотрите, переходит ли ответ в 429. Отдельно проверьте маршрут входа через API (для мобильного приложения) — о нём часто забывают.

2. Пользователя нельзя вычислить

Разные сообщения «Пользователь не найден» и «Неверный пароль» подсказывают атакующему, какой логин существует (user enumeration). Используйте одно общее сообщение: «Неверный логин или пароль». На страницах сброса пароля и регистрации — тот же принцип: «Если такой аккаунт существует, инструкция отправлена». Большая разница во времени ответа тоже даёт информацию, поэтому лучше выполнять проверку хеша пароля и для несуществующего пользователя.

3. Хранение паролей

Пароли хранятся только с помощью адаптивных хеш-функций: bcrypt или Argon2id. MD5, SHA1 или «собственное шифрование» — нет. В Laravel Hash::make() делает это сам; если есть пароли, перенесённые из старой системы, проверьте, что они перехешируются при следующем входе. Минимальная длина и проверка по списку известных утёкших паролей соответствуют рекомендациям OWASP ASVS.

4. OTP-коды

Одноразовый код по SMS или в приложении даёт дополнительную защиту, но и он должен быть построен правильно:

  • Короткий срок действия — несколько минут.
  • Ограниченное число попыток. Шестизначный код без ограничений можно подобрать; после 3–5 ошибок код аннулируется.
  • Одноразовость. Использованный код повторно не принимается.
  • Ограничение повторной отправки. Иначе форма превращается в сервис рассылки SMS за ваш счёт (SMS bombing).
  • Кода нет в ответе и логах. Добавить код в ответ API на время тестов и забыть убрать — частая ошибка.
  • Код привязан к аккаунту. Кодом, отправленным одному пользователю, нельзя войти в другой аккаунт.
  • Проверка на сервере. Не оставляйте решение «код верный» на стороне мобильного клиента.

OTP — дополнительный замок. Если его можно подбирать бесконечно, это не замок.

5. Сброс пароля

Сценарий сброса пароля часто слабее самого входа. В экспертизе нашего собственного продукта внедрение через заголовок Host было опасно именно здесь: поскольку приложение доверяло X-Forwarded-Host, ссылка сброса могла формироваться с доменом атакующего. Исправление — доверять только заголовкам X-Forwarded-For/Proto/Port и принудительно задать базовый URL в production.

Дополнительные проверки:

  • токен сброса случайный, длинный и одноразовый;
  • срок действия ограничен;
  • после смены пароля все остальные сессии и токены аннулируются;
  • запросы на сброс тоже под rate limit.

6. Безопасность сессии

После успешного входа безопасность переходит к сессии:

  • ID сессии обновляется после входа (против session fixation). В Laravel — $request->session()->regenerate().
  • При выходе сессия полностью аннулируется, а не только удаляется cookie: invalidate() и regenerateToken().
  • Флаги cookie: Secure, HttpOnly, SameSite=Lax или Strict.
  • Тайм-аут неактивности. Для финансовых и государственных систем уместен короткий idle timeout.
  • Список устройств и возможность «выйти на других устройствах» — Auth::logoutOtherDevices().

В мобильных приложениях токен сессии должен храниться не в обычных SharedPreferences или UserDefaults, а в Keystore/Keychain. В нашей мобильной экспертизе незашифрованное хранение было находкой высокого уровня и закрыто через flutter_secure_storage. При выходе также очищаются данные WebView.

7. Дополнительный слой для администраторов

Админ-панель должна быть защищена сильнее, чем кабинет обычного пользователя: обязательная двухфакторная аутентификация, по возможности IP allowlist или VPN, отдельный короткий срок сессии. Перенос админки на другой путь сам по себе не защита, но вместе с allowlist уменьшает шум от автоматических сканеров.

8. Логирование и оповещения

Записывайте в лог успешные и неудачные входы, смену пароля, ошибки OTP и блокировки — но не сами пароли и коды. Настройте оповещение о необычно большом числе попыток на один аккаунт или с одного IP. Полезно также уведомлять пользователя о входе с нового устройства.

Быстрая проверка

  1. 15 неверных попыток — появляется 429 или CAPTCHA?
  2. Сообщение одинаково для существующего и несуществующего логина?
  3. Можно ли ввести OTP неверно 10 раз?
  4. Работает ли повторно использованный OTP или ссылка сброса?
  5. Открывается ли страница со старым cookie после выхода?
  6. Закрывается ли сессия на другом устройстве после смены пароля?

Вывод

Защита входа — это не одна функция, а набор небольших правил. Проверив каждое по отдельности, вы подготовите часть системы, которой экспертиза уделяет больше всего внимания.

Sipar проверяет сценарии входа, OTP и сессий до экспертизы и закрывает найденные дыры. Оставьте заявку на сканирование.