7.6 KiB
Démonstration A07:2025 - Authentication Failures
Objectif de la démonstration
Cet atelier illustre concrètement plusieurs vulnérabilités de la catégorie A07:2025 - Authentication Failures (échecs d'authentification) de l'owasp.
Vous n'avez aucun code à écrire. Votre rôle est d'analyser le comportement de deux points de terminaison (endpoints) à l'aide de postman et d'observer les journaux (logs) du serveur.
Contexte du projet
Le code routes/index.js contient deux routes d'authentification:
POST /login-vulnerable: une implémentation intentionnellement faible.POST /login-securise: une implémentation corrigée et sécurisée.
Prérequis
- Node.js et npm installés.
- Le logiciel postman (ou un équivalent pour tester les api).
- Ce projet (avec les
node_modulesinstallés vianpm install).
Instructions de lancement
- Ouvrez un terminal (par exemple, le terminal intégré de webstorm).
- Lancez le serveur en mode "watch" avec la commande:
nodemon ./bin/www - Gardez cette console visible. C'est ici que vous lirez les journaux (logs) du serveur.
- Ouvrez postman pour effectuer les tests décrits ci-dessous.
Partie 1: Analyse du point de terminaison vulnérable
Testez l'endpoint POST http://localhost:3000/login-vulnerable avec postman.
Assurez-vous d'envoyer vos données en raw -> json dans l'onglet Body.
flowchart TD
a["client envoie requête vers /login-vulnerable"] --> b["serveur recherche l'utilisateur"]
b --> c{"utilisateur trouvé ?"}
c -- "non" --> d["retour 404 avec message 'utilisateur non trouvé'"]
c -- "oui" --> e{"mot de passe valable ?"}
e -- "non" --> f["retour 400 avec message 'mot de passe incorrect'"]
e -- "oui" --> g["session non régénérée"]
g --> h["retour 200 'connexion vulnérable réussie'"]
Faille 1: énumération de comptes
L'énumération de comptes permet à un attaquant de deviner quels utilisateurs existent dans la base de données en analysant les différentes réponses du serveur.
Test A: utilisateur inexistant
- Requête (body):
{ "username": "un_utilisateur_inconnu", "password": "123" } - Réponse (status 404):
Erreur : Utilisateur non trouvé.
Test B: utilisateur existant, mauvais mot de passe
- Requête (body):
{ "username": "admin", "password": "mauvais_mot_de_passe" } - Réponse (status 400):
Erreur : Mot de passe incorrect.
Conclusion de la faille 1: Les messages d'erreur sont différents. Un attaquant peut créer un script pour tester des milliers de noms d'utilisateurs et savoir lesquels sont valides (ceux qui retournent "mot de passe incorrect").
Faille 2: mots de passe en clair et absence de limitation
Cette route compare directement le mot de passe reçu avec un mot de passe stocké en clair (password_vulnerable).
flowchart TD
a["client envoie requête vers /login-securise"] --> b["serveur recherche l'utilisateur (sans distinction dans la réponse)"]
b --> c["vérification avec 'bcrypt.compare'"]
c --> d{"identifiants valides ?"}
d -- "non" --> e["retour 401 'identifiants invalides'"]
d -- "oui" --> f["regeneration de la session"]
f --> g["retour 200 'connexion sécurisée réussie'"]
Test C: attaque par force brute
- Le serveur n'implémente aucune limitation de tentatives (rate limiting).
- Un attaquant pourrait tester des millions de mots de passe pour l'utilisateur "admin" sans jamais être bloqué.
Faille 3: fixation de session
La fixation de session se produit lorsque l'identifiant de session d'un utilisateur n'est pas renouvelé après une authentification réussie.
Test D: connexion réussie
-
Requête (body):
{ "username": "admin", "password": "password123" } -
Réponse (status 200):
Connexion (vulnérable) réussie pour admin ! -
Action requise: regardez maintenant la console de votre serveur (dans webstorm).
-
Vous devriez voir les logs suivants:
[VULNÉRABLE] Session AVANT login: [un long identifiant] [VULNÉRABLE] Session APRÈS login: [le même identifiant] (INCHANGÉE)
Conclusion de la faille 3: L'identifiant de session est le même avant et après le login. Si un attaquant parvenait à "fixer" (donner) un identifiant de session à un utilisateur avant sa connexion (par exemple, via un lien piégé), il pourrait usurper son identité une fois l'utilisateur connecté.
Partie 2: Analyse du point de terminaison corrigé
Testez maintenant l'endpoint POST http://localhost:3000/login-securise.
Correction 1: prévention de l'énumération de comptes
Test A (corrigé): utilisateur inexistant
- Requête (body):
{ "username": "un_utilisateur_inconnu", "password": "123" } - Réponse (status 401):
Identifiants invalides.
Test B (corrigé): utilisateur existant, mauvais mot de passe
- Requête (body):
{ "username": "admin", "password": "mauvais_mot_de_passe" } - Réponse (status 401):
Identifiants invalides.
Conclusion de la correction 1: La réponse est identique dans les deux cas. Il est désormais impossible pour un attaquant de distinguer un nom d'utilisateur invalide d'un mot de passe invalide.
Correction 2: hachage et régénération de session
Test D (corrigé): connexion réussie
-
Le code utilise
bcrypt.comparepour comparer de manière sécurisée le mot de passe fourni avec le hash stocké. -
Requête (body):
{ "username": "admin", "password": "password123" } -
Réponse (status 200):
Connexion (sécurisée) réussie pour admin ! -
Action requise: regardez à nouveau la console du serveur.
-
Vous devriez voir ces logs:
[SÉCURISÉ] Session AVANT login: [un premier identifiant] [SÉCURISÉ] Session APRÈS login: [un nouvel identifiant] (CHANGÉE)
Conclusion de la correction 2:
Le serveur a explicitement régénéré la session (req.session.regenerate). L'ancien identifiant est invalidé et un nouveau est créé, empêchant toute attaque de type "fixation de session".
Correction 3: limitation des tentatives (rate limiting)
Test E (corrigé): attaque par force brute
- Envoyez la requête du "Test B (corrigé)" (avec un mauvais mot de passe) 11 fois de suite.
- Les 10 premières tentatives renverront
Identifiants invalides. - À la 11ème tentative (et pour les suivantes pendant 15 minutes), vous recevrez:
- Réponse (status 429):
Trop de tentatives de connexion. Réessayez dans 15 minutes.
Conclusion de la correction 3: Le serveur empêche activement les attaques par force brute ou par "credential stuffing" en limitant le nombre d'échecs autorisés par adresse ip.
Conclusion
Cette démonstration a mis en évidence plusieurs vulnérabilités courantes liées à l'authentification, ainsi que les mesures correctives appropriées pour les atténuer.
flowchart LR
a["vulnérabilité : énumération de comptes"] --> b["risque : découverte des utilisateurs valides"]
c["vulnérabilité : mots de passe en clair"] --> d["risque : compromission immédiate"]
e["vulnérabilité : pas de rate limiting"] --> f["risque : force brute illimitée"]
g["vulnérabilité : session non régénérée"] --> h["risque : fixation de session"]
i["mesure : message d'erreur uniforme"] --> a
j["mesure : mot de passe hashé"] --> c
k["mesure : rate limiting"] --> e
l["mesure : regeneration de session"] --> g