Comprendre et se protéger des attaques CSRF (Cross-Site Request Forgery)
Qu’est-ce qu’une attaque CSRF ?
La CSRF (Cross-Site Request Forgery) est une vulnérabilité web qui permet à un attaquant de forcer un utilisateur authentifié à exécuter des actions non désirées sur une application web, sans son consentement.
Pourquoi ça fonctionne ?
Exemple d’attaque CSRF
-
L'utilisateur est connecté à
bank.demo. Il possède alors un cookie de session :session_id=abc123 -
L’attaquant héberge un site piégé :
malicious.demo -
Ce site contient un formulaire caché comme :
<form action="https://bank.demo/transfer" method="POST"> <input type="hidden" name="to" value="attacker" /> <input type="hidden" name="amount" value="10000" /> <input type="submit" /> </form> <script> document.forms[0].submit(); </script> -
L'utilisateur visite
malicious.demopendant qu'il est encore connecté àbank.demo. -
Le navigateur exécute le JavaScript, soumet le formulaire vers
bank.demoavec le cookie de session. -
Si
bank.demon'applique pas de protections CSRF, il pense que la requête est légitime, et effectue le virement.
Comment se protéger des attaques CSRF ?
Défense 1 : Attribut SameSite sur les cookies
L'attribut SameSite limite quand un cookie peut être envoyé automatiquement dans une requête inter-sites :
| Valeur | Description | Protection |
|---|---|---|
SameSite=Strict |
Le cookie n’est jamais envoyé dans une requête initiée par un autre site | 🔒 Très forte (mais peut casser des cas d’usage) |
SameSite=Lax (valeur par défaut) |
Le cookie est envoyé pour les navigations GET, mais pas pour les requêtes sensibles (POST/PUT/DELETE) | ⚠️ Bonne, mais pas suffisante |
SameSite=None |
Le cookie est envoyé dans toutes les requêtes, même cross-site | ❌ Aucune protection sans autre mécanisme |
💡 Limite de SameSite=Strict :
Dans les architectures modernes avec une SPA et une API sur des domaines séparés, SameSite=Strict bloque même les requêtes légitimes du frontend vers l’API.
Défense 2 : Vérification des en-têtes Origin ou Referer
Le backend peut valider l’origine de chaque requête :
-
Si
Origin !== https://bank.demo→ Rejeter la requête -
Si
Refererne commence pas parhttps://bank.demo→ Rejeter aussi
📌 Avantages :
-
Ces en-têtes sont automatiquement ajoutés par le navigateur.
-
Un site externe ne peut pas les falsifier dans une requête HTML ou via JavaScript classique.
⚠️ Limites :
-
Le
Refererpeut être absent par exemple dans un navigateur configuré en mode privé. -
Cette méthode ne protège pas contre les attaques manuelles (ex: via
curlavec des cookies volés).
curl -X POST https://banque.demo/transfer \
-H "Origin: https://banque.demo" \
-H "Cookie: session_id=abc123"
✅ Défense 3 (la plus fiable) : Jeton anti-CSRF (CSRF Token)
Un jeton CSRF est une valeur aléatoire unique générée côté serveur, liée à la session utilisateur.
-
Il est injecté dans le HTML (dans un formulaire ou une balise meta)
-
Le navigateur ne l’envoie pas automatiquement : c’est au client (JavaScript) de l’ajouter explicitement à chaque requête.
Exemple : SPA avec cookies + CSRF Token
fetch('/api/update-profile', {
method: 'POST',
credentials: 'include', // envoie les cookies
headers: {
'X-CSRF-Token': csrfToken // ajouté volontairement
},
body: JSON.stringify({...})
});
Le serveur compare :
-
X-CSRF-Token(reçu) -
Avec le token stocké en session côté serveur
Si ça ne correspond pas → Requête rejetée (403)
Pourquoi ça protège ?
Parce que le site malveillant ne peut pas lire le HTML ou les données JavaScript de bank.demo (grâce à la politique de même origine). Donc, il ne connaît pas le token et ne peut pas le réutiliser.
Et si on n’utilise pas de cookie ?
Authentification par JWT dans le header Authorization
Si tu n’utilises pas de cookies, mais que tu stockes le token JWT côté client et que tu l’envoies manuellement :
fetch('/api/protected', {
method: 'POST',
headers: {
'Authorization': 'Bearer <token>',
'Content-Type': 'application/json'
},
body: JSON.stringify({...})
});
➡️ Aucune vulnérabilité CSRF possible, car le navigateur ne fait rien automatiquement.
⚠️ Mais attention :
-
Si tu stockes le JWT dans
localStorageousessionStorage, il peut être volé via une attaque XSS. -
Il est donc essentiel de se prémunir contre les scripts malveillants en appliquant des règles strictes de Content Security Policy (CSP).
Résumé : Quand faut-il un token CSRF ?
| Type d’authentification | CSRF possible ? | CSRF Token requis ? | Pourquoi ? |
|---|---|---|---|
Cookie de session (session_id) |
✅ Oui | ✅ Oui | Cookies envoyés automatiquement |
| JWT dans cookie (HttpOnly) | ✅ Oui | ✅ Oui | Même problème que les sessions |
JWT dans header Authorization |
❌ Non | ❌ Non | Rien n’est envoyé automatiquement |
JWT dans localStorage ou mémoire JS |
❌ Non | ❌ Non | Requêtes manuelles, pas CSRF |
Bonnes pratiques
-
Utilise au minimum
SameSite=Laxsur les cookies -
Active la protection CSRF pour toutes requêtes modifiantes (POST/PUT/DELETE)
-
Ne mets jamais le token CSRF dans un cookie sinon, il sera envoyé automatiquement → la protection devient inefficace.
-
Ne te fie pas uniquement à
OriginouReferer, surtout pour des opérations sensibles -
Préfère l’auth avec
Authorization: Bearer(sans cookie) pour éliminer totalement les risques CSRF -
Protège-toi contre le XSS si tu stockes des tokens dans
localStorage