198 lines
7.6 KiB
Markdown
198 lines
7.6 KiB
Markdown
# 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:
|
|
1. `POST /login-vulnerable`: une implémentation intentionnellement faible.
|
|
2. `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_modules` installés via `npm install`).
|
|
|
|
## Instructions de lancement
|
|
|
|
1. Ouvrez un terminal (par exemple, le terminal intégré de webstorm).
|
|
2. Lancez le serveur en mode "watch" avec la commande:
|
|
```bash
|
|
nodemon ./bin/www
|
|
```
|
|
3. Gardez cette console visible. C'est ici que vous lirez les journaux (logs) du serveur.
|
|
4. 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`.
|
|
|
|
```mermaid
|
|
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):
|
|
```json
|
|
{
|
|
"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):
|
|
```json
|
|
{
|
|
"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`).
|
|
|
|
```mermaid
|
|
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):
|
|
```json
|
|
{
|
|
"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):
|
|
```json
|
|
{
|
|
"username": "un_utilisateur_inconnu",
|
|
"password": "123"
|
|
}
|
|
```
|
|
* Réponse (status 401): `Identifiants invalides.`
|
|
|
|
**Test B (corrigé): utilisateur existant, mauvais mot de passe**
|
|
* Requête (body):
|
|
```json
|
|
{
|
|
"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.compare` pour comparer de manière sécurisée le mot de passe fourni avec le hash stocké.
|
|
* Requête (body):
|
|
```json
|
|
{
|
|
"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.
|
|
|
|
```mermaid
|
|
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
|
|
``` |