Дві різні атаки на вхід:
- перебір (brute force) - багато паролів для одного облікового запису;
- credential stuffing - пари «email + пароль» з витоків інших сайтів перевіряються на вашому. Працює, бо люди повторюють паролі. Спроб на один обліковий запис мало - тож простий ліміт на обліковий запис цю атаку не бачить.
Захист - кілька шарів:
1. Обмеження частоти - за комбінацією облікового запису й IP і окремо за IP:
RateLimiter::for('login', fn (Request $request) => [
Limit::perMinute(5)->by(Str::lower($request->input('email')).'|'.$request->ip()),
Limit::perMinute(30)->by($request->ip()),
]);
Laravel Fortify і стартові набори вже обмежують спроби входу.
2. Багатофакторна автентифікація - найефективніший захист від обох атак: правильний пароль без другого фактора нічого не дає.
3. Заборона скомпрометованих паролів при реєстрації й зміні пароля:
'password' => ['required', 'confirmed', Password::min(12)->uncompromised()],
uncompromised() перевіряє пароль у базі витоків Have I Been Pwned методом k-анонімності: на сервіс надсилаються лише перші 5 символів SHA-1-хешу, а не сам пароль.
4. Виявлення аномалій: вхід з нової країни чи пристрою - сповіщення користувачу, додаткова перевірка.
5. CAPTCHA чи невидимі перевірки (Cloudflare Turnstile) - після кількох невдач чи при підозрілому трафіку, а не завжди.
6. Захист на рівні мережі: WAF, ліміти на CDN, блокування відомих ботнетів.
Чого уникати:
- постійне блокування облікового запису після N невдач - зловмисник легко заблокує будь-кого (DoS для користувачів). Краще тимчасові затримки й CAPTCHA;
- різні повідомлення «невірний пароль» і «користувача не знайдено» - це перелік зареєстрованих email;
- ліміт лише за IP - атакують через тисячі адрес;
- ліміт лише за обліковим записом - credential stuffing його не досягає.
Моніторинг: різке зростання невдалих входів з різних IP на різні облікові записи - ознака credential stuffing, потрібна реакція (посилені перевірки, повідомлення користувачам).
Докладніше в документації: OWASP: запобігання credential stuffing