Впровадження обмеження спроб входу (захист від перебору паролів) на сайті
Атаки перебору паролів - один з найпоширеніших векторів компрометації облікових записів. Без обмежень зловмисники можуть перевіряти тисячі паролів за хвилину за допомогою автоматизованих скриптів.
Стратегії захисту
Захист від перебору паролів будується з кількох рівнів:
- Rate limiting — обмеження кількості спроб за період
- Account lockout — тимчасове блокування облікового запису
- IP блокування — блокування джерела атаки
- Progressive delays — зростаючі затримки між спробами
- CAPTCHA — після N невдалих спроб
Реалізація на Laravel
// app/Http/Controllers/Auth/LoginController.php
class LoginController extends Controller
{
protected int $maxAttempts = 5;
protected int $decayMinutes = 15;
public function login(Request $request)
{
// Вбудований throttle Laravel використовує кеш (Redis/database)
if ($this->hasTooManyLoginAttempts($request)) {
$this->fireLockoutEvent($request);
return $this->sendLockoutResponse($request);
}
if (Auth::attempt($request->only('email', 'password'))) {
$this->clearLoginAttempts($request);
return redirect()->intended('/dashboard');
}
$this->incrementLoginAttempts($request);
return back()->withErrors(['email' => 'Невірні облікові дані']);
}
}
Трейт ThrottlesLogins використовує ключ виду email|IP — блокує конкретну комбінацію, не весь IP.
Користувацька реалізація через Redis
class BruteForceProtection
{
private Redis $redis;
private int $maxAttempts = 5;
private int $lockoutSeconds = 900; // 15 хвилин
private int $windowSeconds = 300; // 5 хвилин
public function attempt(string $key): bool
{
$redisKey = "login_attempts:{$key}";
$count = $this->redis->incr($redisKey);
if ($count === 1) {
$this->redis->expire($redisKey, $this->windowSeconds);
}
if ($count > $this->maxAttempts) {
$this->redis->setex("lockout:{$key}", $this->lockoutSeconds, 1);
return false;
}
return true;
}
public function isLocked(string $key): bool
{
return (bool) $this->redis->exists("lockout:{$key}");
}
public function getLockoutTtl(string $key): int
{
return $this->redis->ttl("lockout:{$key}");
}
public function reset(string $key): void
{
$this->redis->del("login_attempts:{$key}", "lockout:{$key}");
}
}
Progressive Delay (зростаючі затримки)
Замість повної блокування — збільшення затримки з кожною невдалою спробою:
// Затримки: 1с → 2с → 4с → 8с → 16с → 32с (максимум)
$delay = min(pow(2, $attempts - 1), 32);
sleep($delay);
Це уповільнює автоматичний перебір без повного отримання функції входу.
Диференціація блокувань
Важливо розділяти рівні блокування:
| Рівень | Ключ | Умова | Тривалість |
|---|---|---|---|
| За email | login:email:[email protected] |
5 спроб за 5 хв | 15 хв |
| За IP | login:ip:1.2.3.4 |
20 спроб за 5 хв | 30 хв |
| Глобальна | login:global |
1000 спроб за 1 хв | Алерт |
Блокування лише за IP може зашкодити користувачам за NAT/проксі. Блокування лише за email легко обійти з іншого IP.
CAPTCHA після кількох невдалих спроб
// У контролері
$attempts = $this->limiter->attempts($throttleKey);
$showCaptcha = $attempts >= 3;
return view('auth.login', compact('showCaptcha'));
@if ($showCaptcha)
<div class="cf-turnstile" data-sitekey="{{ config('services.turnstile.site_key') }}"></div>
@endif
Сповіщення користувача
При успішному вході після кількох невдалих спроб — поштове сповіщення:
// Після успішної аутентифікації
if ($previousFailedAttempts > 2) {
Mail::to($user)->queue(new SuspiciousLoginNotification($request->ip()));
}
Моніторинг
Логування всіх невдалих спроб у структурованому форматі:
Log::warning('Failed login attempt', [
'email' => $request->email,
'ip' => $request->ip(),
'user_agent' => $request->userAgent(),
'timestamp' => now()->toIso8601String(),
]);
Алерт у Grafana/Datadog при піку подій невдалого входу — ознака активної атаки.
Тривалість реалізації
- Базовий throttle Laravel: 1 день
- Користувацька реалізація Redis з прогресивними затримками: 2–3 дні
- Інтеграція з моніторингом та сповіщеннями: +1 день







