Aller au contenu

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é :

🐬 SQL
   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 Ă  jour last_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).
  • 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
<?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>