Защита от brute-force на уровне приложения

Brute-force атаки на форму аутентификации основаны на переборе паролей до получения успешного входа. В отличие от классических переборов, современные атаки используют распределённые ботнеты, словари утекших паролей и высокоскоростные HTTP-клиенты. Основная цель — обойти механизм проверки пароля, а не угадать его вручную.

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

Почему bcrypt сам по себе не является полной защитой

bcrypt — это адаптивная функция хэширования паролей, основанная на Blowfish. Она специально спроектирована так, чтобы:

  • замедлять вычисление хэша;
  • усложнять GPU/ASIC-ускорение;
  • поддерживать настройку стоимости (cost factor);
  • включать соль по умолчанию.

Однако важно понимать ограничение: bcrypt защищает хранимые пароли, но не защищает endpoint логина от перебора запросов.

Если атакующий может отправлять 10 000 запросов в секунду к /login, bcrypt лишь делает каждый запрос дорогим, но не блокирует саму возможность атаки.

Стоимость вычислений и cost factor

Ключевой параметр bcrypt — cost factor (обычно обозначается как saltRounds в Node.js реализации).

Он задаёт количество итераций:

  • чем выше значение — тем медленнее хэширование;
  • рост стоимости экспоненциальный: увеличение на 1 удваивает время вычисления.

Пример использования:

import bcrypt fr om 'bcryptjs';

const password = 'user_password';

const saltRounds = 12;

const hash = await bcrypt.hash(password, saltRounds);

Практическое влияние:

  • 10 — быстро, но слабее против GPU;
  • 12–14 — баланс безопасности и производительности;
  • 15+ — часто уже создаёт нагрузку на сервер при массовых логинах.

Важно учитывать, что увеличение cost factor повышает устойчивость к перебору, но одновременно усиливает эффект DoS при атаке на логин-эндпоинт.

Проверка пароля и точка входа атаки

Типичный процесс проверки:

const isMatch = await bcrypt.compare(password, user.passwordHash);

Именно этот вызов становится «дорогой операцией», которую атакующий может эксплуатировать.

Если система не ограничивает частоту вызовов, злоумышленник получает возможность:

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

Rate limiting как основной слой защиты

Первый обязательный механизм — ограничение частоты запросов.

Часто используется middleware на уровне Express:

import rateLimit fr om 'express-rate-lim it';

const loginLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 20,
  standardHeaders: true,
  legacyHeaders: false,
});

app.post('/login', loginLimiter, loginHandler);

Принцип:

  • ограничение действует на IP или ключ идентификации;
  • сброс окна происходит по времени;
  • превышение лимита блокирует дальнейшие попытки.

Ограничение не только по IP

IP-лимиты недостаточны из-за:

  • NAT (много пользователей за одним IP);
  • прокси и мобильных сетей;
  • распределённых атак.

Поэтому добавляется вторичный слой:

  • лимит по username/email;
  • лимит по device fingerprint;
  • комбинированные ключи (IP + userId).

Пример логики:

const key = `${ip}:${email}`;

Progressive delay (увеличивающаяся задержка)

Альтернатива жёсткой блокировке — увеличение задержки ответа.

const delay = Math.min(1000 * 2 ** failedAttempts, 30000);
await new Promise(r => setTimeout(r, delay));

Эффект:

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

Account lockout: преимущества и риски

Механизм блокировки аккаунта после N неудачных попыток:

  • 5–10 попыток → временная блокировка;
  • сброс через таймаут или email-подтверждение.

Пример логики:

if (user.failedAttempts >= 5) {
  user.lockUntil = Date.now() + 15 * 60 * 1000;
}

Однако этот подход имеет риски:

  • позволяет атакующему блокировать чужие аккаунты (DoS на пользователей);
  • требует аккуратного баланса;
  • часто заменяется мягкими ограничениями.

Защита от timing attacks

Даже при неправильном пароле система должна вести себя одинаково по времени.

Опасность возникает, если:

  • быстрый ответ при «пользователь не найден»;
  • медленный ответ при bcrypt.compare.

Это позволяет угадывать существование аккаунта.

Решение:

  • всегда выполнять bcrypt.compare;
  • использовать фиктивный хэш при отсутствии пользователя:
const fakeHash = '$2a$12$invalidsaltinvalidsaltinv';

await bcrypt.compare(password, user?.passwordHash || fakeHash);

Предотвращение enumeration пользователей

Brute-force часто комбинируется с перебором email/логинов.

Защита включает:

  • одинаковые ответы API:

    • «неверные данные» без уточнений;
  • одинаковое время ответа;

  • отсутствие различий в HTTP статусах между ошибками.

CAPTCHA как внешний барьер

CAPTCHA используется как дополнительный слой после:

  • нескольких неудачных попыток;
  • подозрительного поведения;
  • входа из нового региона.

Она не заменяет bcrypt или rate limiting, а дополняет их, снижая автоматизацию атак.

Распределённые атаки и масштабирование защиты

В микросервисной архитектуре защита должна быть централизованной:

  • Redis для хранения counters;
  • shared rate lim it state;
  • централизованный auth gateway.

Пример Redis-based throttling:

await redis.incr(`login:${ip}`);
await redis.expire(`login:${ip}`, 900);

Это критично, поскольку локальный rate limiting не работает в кластере Node.js.

Логирование и мониторинг атак

Без наблюдаемости защита неэффективна.

Фиксируются:

  • количество неудачных логинов;
  • пики запросов;
  • подозрительные IP;
  • повторяющиеся username attempts.

Метрики позволяют:

  • выявлять брутфорс-кампании;
  • динамически ужесточать лимиты;
  • включать защитные режимы.

Практическая архитектура защиты

Комбинированная схема обычно включает:

  • bcrypt.js с cost factor 12–14;
  • rate limiting (IP + user);
  • progressive delay;
  • одинаковые ответы API;
  • защита от enumeration;
  • Redis для распределённых лимитов;
  • мониторинг и алерты;
  • CAPTCHA при аномалиях.

Такой набор создаёт многоуровневую систему, где bcrypt отвечает только за криптографическую стойкость хранения, а остальные механизмы блокируют саму возможность эффективного перебора.