TP Protection contre la force brute đâïž
đŻ Objectifs pĂ©dagogiques
- Comprendre comment détecter une attaque par force brute.
- Implémenter des mécanismes de défense progressifs sur la page de login.
- Appliquer des bonnes pratiques de sĂ©curitĂ© sans dĂ©grader lâexpĂ©rience utilisateur.
1. Contexteâïž
Vous disposez dĂ©jĂ dâune application Web avec :
- un systĂšme dâinscription / connexion,
- une authentification multi-facteur (MFA) par TOTP.
Mais actuellement, un attaquant pourrait tenter des milliers de mots de passe sur un mĂȘme compte sans contrainte.
đ Vous devez protĂ©ger la page de login contre les attaques par force brute.
2. Travail guidĂ©âïž
1.Modifier la base de données users
pour ajouter des colonnes de sécurité :
ALTER TABLE users
ADD failed_attempts INT NOT NULL DEFAULT 0,
ADD last_failed_at TIMESTAMP NULL DEFAULT NULL,
ADD lock_until TIMESTAMP NULL DEFAULT NULL;
2.Mettre Ă jour login.php
pour :
- Vérifier si le compte est verrouillé (
lock_until > NOW()
) â refuser la connexion. -
En cas dâĂ©chec de login :
- Incrémenter
failed_attempts
et mettre Ă jourlast_failed_at
. -
Appliquer une politique :
-
aprĂšs 5 Ă©checs consĂ©cutifs â ajouter un dĂ©lai (sleep).
- aprĂšs 10 Ă©checs â verrouiller le compte 15 minutes (
lock_until
).
- Incrémenter
-
En cas de succĂšs â rĂ©initialiser
failed_attempts
.
3.Bonus (optionnel) :
- Protection par IP : créer une table
login_attempts (ip, attempts, last_attempt)
. - AprĂšs 100 tentatives depuis une IP â bloquer 1 h. (utiliser un script python avec
`requests
pour lancer les 100 requĂȘtes HTTP) - Afficher un message gĂ©nĂ©rique (« Identifiants incorrects ») sans indiquer si lâemail existe.
Comportement attendu :
- Si un utilisateur se trompe 3 fois â toujours possible de rĂ©essayer.
- Sâil se trompe 8 fois â dĂ©lai progressif (ex. +2 s).
- Sâil se trompe 12 fois â compte bloquĂ© 15 minutes.
-
AprĂšs un login correct â compteur rĂ©initialisĂ©.
-
â SĂ©curitĂ© : messages neutres, verrous effectifs, remise Ă zĂ©ro aprĂšs succĂšs.
- â Bonus : prise en compte IP, captcha ou notification utilisateur.
Checklist de Tests đ
- Créez un compte test.
- Essayez 3 mauvais mots de passe â pas de blocage.
- Faites 6 Ă©checs â dĂ©lai avant rĂ©ponse visible.
- Faites 12 Ă©checs â verrouillage 15 min.
- AprĂšs un bon mot de passe â compteur rĂ©initialisĂ©.
3. Jeu de testsâïž
ID | Test | PrĂ©conditions | Ătapes | RĂ©sultat attendu | RĂ©sultat obtenu |
---|---|---|---|---|---|
B1 | Schéma DB mis à jour | schema_tp2.sql appliqué |
Vérifier colonnes failed_attempts , lock_until |
Colonnes présentes | |
B2 | Ăchec simple incrĂ©mente compteur | Compte existant | Tenter login avec mot de passe incorrect 1 fois | failed_attempts = 1, last_failed_at mis Ă jour |
|
B3 | DĂ©lai progressif (backoff) | AprĂšs 5 Ă©checs | Tenter 6á” Ă©chec et mesurer latence | RĂ©ponse retardĂ©e (â„1s et augmentant) | |
B4 | Verrouillage temporaire | AprÚs 10 échecs consécutifs | Faire 10ᔠpuis 11ᔠéchec | lock_until défini (NOW + 15min), messages appropriés |
|
B5 | Tentative pendant lock | Compte verrouillé | Essayer de se connecter pendant lock | Refus immédiat avec message verrou | |
B6 | Réinitialisation aprÚs succÚs | Compte avec failed_attempts >0 |
Réussir login avec mot de passe correct | failed_attempts remis à 0, lock_until NULL |
|
B7 | Message générique | N'importe quel échec (user inconnu ou mdp mauvais) | Essayer login email inexistant | Affichage toujours "Identifiants incorrects" | |
B8 | Limite IP (soft) â bonus | Table login_attempts prĂ©sente |
Simuler >100 tentatives depuis mĂȘme IP en 1h | RĂ©ponse : message "Trop de tentatives" ou blocage | |
B9 | Interaction MFA + lock | Compte MFA activé et failed_attempts élevé |
AprĂšs verrouillage, tenter bypass MFA | Impossible d'atteindre mfa_verify.php sans mot de passe correct |
|
B10 | Logs & traçabilité (manuel) | Logging activé si prévu | Réaliser échecs et succÚs | Entrées logs avec IP, timestamp, user, résultat | |
B11 | RemĂ©diation utilisateur | Compte verrouillĂ© | Suivre procĂ©dure (mail ou support) â si implĂ©mentĂ©e | Utilisateur reçoit notification / peut dĂ©bloquer via procĂ©dure |
Corrigé
Exemple login.php
(simplifié avec anti-bruteforce)
<?php require __DIR__ . '/config.php'; ?>
<!DOCTYPE html>
<html lang="fr">
<head><meta charset="UTF-8"><title>Connexion sécurisée</title></head>
<body>
<h1>Connexion</h1>
<?php
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
$email = filter_input(INPUT_POST, 'email', FILTER_VALIDATE_EMAIL);
$password = $_POST['password'] ?? '';
if ($email && $password) {
$stmt = $pdo->prepare('SELECT * FROM users WHERE email = ?');
$stmt->execute([$email]);
$user = $stmt->fetch();
if ($user) {
// Vérifier si verrouillé
if ($user['lock_until'] && strtotime($user['lock_until']) > time()) {
echo '<p style="color:red">Compte temporairement verrouillé. Réessayez plus tard.</p>';
} elseif (password_verify($password, $user['password_hash'])) {
// SuccĂšs â reset compteur
$pdo->prepare('UPDATE users SET failed_attempts=0, lock_until=NULL WHERE id=?')
->execute([$user['id']]);
$_SESSION['user_id'] = $user['id'];
$_SESSION['user_email'] = $user['email'];
if ($user['mfa_enabled']) {
$_SESSION['mfa_required'] = true;
header('Location: mfa_verify.php');
} else {
header('Location: mfa_setup.php');
}
exit;
} else {
// Ăchec â incrĂ©menter
$failed = $user['failed_attempts'] + 1;
$pdo->prepare('UPDATE users SET failed_attempts=?, last_failed_at=NOW() WHERE id=?')
->execute([$failed, $user['id']]);
if ($failed >= 10) {
// Verrouillage 15 minutes
$pdo->prepare('UPDATE users SET lock_until=? WHERE id=?')
->execute([date('Y-m-d H:i:s', time() + 15*60), $user['id']]);
echo '<p style="color:red">Compte verrouillé 15 minutes.</p>';
} else {
// Délai progressif (anti-bruteforce)
$delay = max(0, $failed - 4); // à partir du 5e échec
if ($delay > 0) sleep($delay);
echo '<p style="color:red">Identifiants incorrects.</p>';
}
}
} else {
// Compte inexistant â message neutre
echo '<p style="color:red">Identifiants incorrects.</p>';
}
}
}
?>
<form method="post">
<label>Email <input type="email" name="email" required></label><br>
<label>Mot de passe <input type="password" name="password" required></label><br>
<button type="submit">Se connecter</button>
</form>
<p><a href="register.php">Créer un compte</a></p>
</body></html>