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.demo
pendant qu'il est encore connecté àbank.demo
. -
Le navigateur exécute le JavaScript, soumet le formulaire vers
bank.demo
avec le cookie de session. -
Si
bank.demo
n'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
Referer
ne 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
Referer
peut être absent par exemple dans un navigateur configuré en mode privé. -
Cette méthode ne protège pas contre les attaques manuelles (ex: via
curl
avec 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
localStorage
ousessionStorage
, 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=Lax
sur 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 à
Origin
ouReferer
, 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