Une vulnérabilité CSRF (Cross-Site Request Forgery, en français « falsification de requête inter-sites », classée CWE-352, #3 du CWE Top 25 MITRE 2025) est une faille web qui permet à un attaquant de forcer le navigateur d'un utilisateur authentifié à exécuter une action non désirée sur un site de confiance où cet utilisateur est connecté. Le mécanisme exploite le fonctionnement de base du web : les navigateurs envoient automatiquement les cookies de session d'un site à ce site, quelle que soit l'origine qui a déclenché la requête. Un attaquant publie une page piégée (formulaire caché, image, iframe) qui, lorsqu'elle est chargée par un utilisateur connecté au site cible, déclenche une requête authentifiée vers ce site — virement bancaire, changement de mot de passe, suppression de compte, modification administrative. Les défenses 2026 combinent SameSite cookies (Lax enforced par défaut dans Chromium, Firefox, Safari depuis 2020-2021), synchronizer token pour les applications stateful, double submit cookie pour les applications stateless, et validation de l'en-tête Origin en defense-in-depth. Cet article explique le mécanisme CSRF pas à pas avec exemples de code, les variantes (GET-based, POST-based, login CSRF, JSON CSRF), les défenses à implémenter en 2026 y compris dans les SPA et APIs modernes, et les mythes de prévention à ne pas confondre.
1. Ce qu'est une vulnérabilité CSRF
1.1 Définition technique
CSRF est une vulnérabilité qui permet à un attaquant de déclencher des actions authentifiées au nom d'un utilisateur sans que ce dernier en ait connaissance, en exploitant le fait que les navigateurs web attachent automatiquement les cookies de session à toute requête sortante vers un domaine donné, peu importe qui initie la requête.
Quatre éléments rendent une application vulnérable à CSRF :
- L'application gère une session authentifiée via un cookie (ou un autre mécanisme automatiquement envoyé par le navigateur, comme HTTP Basic Auth ou les certificats clients).
- Elle expose des endpoints state-changing (POST, PUT, PATCH, DELETE, ou GET mal conçus qui modifient l'état).
- La requête vers ces endpoints est reproductible depuis une origine externe (paramètres connus ou devinables).
- L'application ne valide pas l'origine de la requête au-delà de la présence du cookie.
1.2 Ce que CSRF n'est pas
- Pas une injection de code côté serveur ou client (c'est XSS, CWE-79).
- Pas une fuite de cookie (c'est Cookie Theft via XSS ou session fixation).
- Pas une attaque brute force sur le mot de passe.
- Pas un détournement d'une faille d'autorisation (c'est Broken Access Control, OWASP A01).
CSRF est spécifiquement l'abus du navigateur comme proxy authentifié vers le site cible.
2. Le mécanisme pas à pas
2.1 Le scénario classique
Alice est authentifiée sur banque.example.test avec un cookie de session valide. Elle ouvre un onglet et visite malveillant.example.test (trouvé via phishing, ad malicieuse, forum piégé, ou site légitime compromis).
malveillant.example.test sert la page HTML suivante :
<!-- exemple-page-attaquant.html -->
<!-- Page hebergee sur un domaine controle par l'attaquant.
Code donne a des fins pedagogiques et defensives uniquement. -->
<!DOCTYPE html>
<html>
<head><title>Vous avez gagne un cadeau !</title></head>
<body>
<h1>Felicitations, vous avez gagne un iPhone !</h1>
<p>Cliquez pour reclamer votre cadeau...</p>
<!-- Formulaire cache auto-soumis vers la banque cible -->
<form id="csrf-form"
action="https://banque.example.test/api/v1/virements"
method="POST"
style="display:none">
<input type="hidden" name="destinataire" value="FR76...BIC attaquant" />
<input type="hidden" name="montant" value="5000" />
<input type="hidden" name="motif" value="Remboursement" />
</form>
<script>
// Soumission automatique au chargement de la page
document.getElementById('csrf-form').submit();
</script>
</body>
</html>2.2 Ce qui se passe dans le navigateur d'Alice
- Alice charge la page piégée.
- Le script soumet automatiquement le formulaire vers
banque.example.test. - Le navigateur construit la requête POST avec les paramètres du formulaire.
- Cruciale : le navigateur ajoute automatiquement le cookie de session
banque.example.testà la requête (règle standard : les cookies sont envoyés au domaine auquel ils appartiennent, peu importe l'origine qui déclenche la requête — sauf restrictions SameSite). banque.example.testreçoit la requête. Le cookie étant valide, elle reconnaît Alice. Elle exécute le virement.- Alice voit éventuellement un bref clignotement ou rien du tout. Quand elle retourne sur sa banque, les 5 000 € ont disparu.
2.3 Sans interaction utilisateur
Pire scénario : même un clic n'est pas nécessaire. Un simple <img src="https://banque.example.test/api/v1/virements?destinataire=..."> dans un email HTML ouvert par Alice déclenche la requête GET (si la banque accepte les virements en GET, ce qui est une faute grave mais arrive).
3. Pourquoi le mécanisme existe : l'ambient authority des cookies
3.1 Conception historique du web
Les cookies ont été conçus en 1994 par Netscape pour maintenir une session entre requêtes HTTP (qui sont nativement stateless). Le choix de design : les cookies sont envoyés automatiquement par le navigateur à chaque requête vers leur domaine propriétaire, sans que le site appelant ne puisse les lire. Ce design s'appelle ambient authority — l'autorité est ambiante, présente dès que la requête existe.
Ce comportement est la base du web moderne (sessions, panier d'achat, préférences persistantes) mais crée intrinsèquement la surface CSRF : toute page tierce qui déclenche une requête vers ton site fera voyager les cookies de session de tes utilisateurs.
3.2 Same-Origin Policy (SOP) vs cookies
La Same-Origin Policy (SOP, RFC implicite, formalisée dans HTML5) empêche un script JavaScript hébergé sur un domaine A de lire la réponse d'une requête vers un domaine B. Mais elle n'empêche pas l'envoi de la requête vers B, et elle n'empêche pas les cookies de B d'être attachés à cette requête.
Conséquence pour CSRF : l'attaquant ne peut pas lire la réponse, mais il peut déclencher l'action côté serveur, ce qui suffit pour la plupart des attaques (modifier un état, pas exfiltrer des données).
4. Les variantes de CSRF
4.1 GET-based CSRF
L'application accepte des actions state-changing en GET. Exploitation triviale via <img>, <iframe>, <link>, ou simplement un lien cliqué.
Règle absolue : ne jamais accepter d'action state-changing en GET. Toute mutation d'état passe par POST, PUT, PATCH ou DELETE. Cette règle est dans la RFC 9110 (HTTP Semantics) section 9.2.1 (méthodes safe).
4.2 POST-based CSRF
La variante classique, exploitée via formulaire HTML auto-soumis comme dans l'exemple ci-dessus. Nécessite un chargement de page (pas juste une image), mais reste trivial sur navigateurs modernes.
4.3 JSON CSRF
L'application attend un body JSON (Content-Type: application/json), pas application/x-www-form-urlencoded. Le navigateur envoie automatiquement un formulaire HTML en application/x-www-form-urlencoded — donc un formulaire HTML classique ne marche pas.
Mais : l'attaquant peut contourner via fetch() avec mode: 'no-cors' (qui accepte application/json en Content-Type mais se transforme en text/plain sans CORS) ou via un formulaire HTML avec enctype="text/plain". Si l'application accepte Content-Type: text/plain et parse le body comme du JSON, CSRF JSON exploitable. Les applications strictes qui rejettent tout Content-Type autre que application/json avec un preflight CORS sont protégées.
4.4 Login CSRF
L'attaquant soumet ses propres credentials dans un formulaire de login CSRF. La victime se retrouve silencieusement connectée au compte de l'attaquant. Tout ce que la victime fait ensuite (upload de documents sensibles, envoi de messages) est enregistré dans le compte contrôlé par l'attaquant.
Les tokens CSRF classiques ne protègent pas contre le login CSRF car l'utilisateur n'a pas encore de session. Défense : token CSRF pré-session, cookies SameSite, ou vérification de l'Origin header.
5. Les défenses CSRF en 2026
5.1 SameSite cookies : la défense par défaut
L'attribut SameSite sur un cookie indique au navigateur s'il doit être envoyé dans les requêtes cross-origin.
| Valeur | Comportement |
|---|---|
Strict | Cookie jamais envoyé dans une requête cross-origin, même navigation top-level. Sécurité maximale, UX dégradée (perte de session sur lien externe entrant). |
Lax | Cookie envoyé uniquement sur navigation top-level GET (clic lien direct). Bloque les POST CSRF. Valeur par défaut depuis Chrome 80 (fév 2020), Firefox 96 (jan 2022), Safari 17+. |
None | Cookie envoyé dans toutes les requêtes cross-origin. Exige l'attribut Secure (HTTPS uniquement). À utiliser uniquement pour les cas légitimes d'iframe embed cross-origin. |
Règle pratique 2026 : SameSite=Lax par défaut, Strict pour les cookies ultra-sensibles (admin, banking), None uniquement avec Secure et raison métier documentée.
Limite importante : SameSite=Lax ne bloque pas les navigations top-level GET (lien cliqué). Si l'application accepte un GET state-changing, SameSite ne suffit pas. Défense complémentaire obligatoire.
5.2 Synchronizer token pattern (applications stateful)
L'application génère un token aléatoire lié à la session de l'utilisateur, l'intègre dans chaque formulaire HTML (hidden input) ou header custom, et vérifie côté serveur à chaque requête state-changing.
// exemple-synchronizer-token-express.js
// Defense CSRF avec synchronizer token dans une app Express stateful.
// Utilise csurf (deprecated) ou son successeur : csrf-sync pour stateful.
//
// Prerequis : session cote serveur (express-session avec store Redis).
const express = require('express');
const session = require('express-session');
const { csrfSync } = require('csrf-sync');
const { generateToken, csrfSynchronisedProtection } = csrfSync();
const app = express();
app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false }));
app.use(express.urlencoded({ extended: true }));
app.use(csrfSynchronisedProtection);
// Endpoint qui genere le token et le renvoie au front
app.get('/csrf-token', (req, res) => {
res.json({ csrfToken: generateToken(req) });
});
// Endpoint state-changing protege
app.post('/api/profile/update', (req, res) => {
// Le middleware csrfSynchronisedProtection verifie automatiquement
// que req.headers['x-csrf-token'] matche le token en session.
// En cas d'echec : 403 Forbidden renvoye automatiquement.
updateUserProfile(req.session.userId, req.body);
res.json({ ok: true });
});5.3 Double submit cookie (applications stateless)
Pour les applications sans session côté serveur (APIs JWT, architectures stateless), le pattern double submit cookie :
- Le serveur génère une valeur aléatoire.
- Il l'envoie au navigateur dans un cookie (non-httpOnly pour permettre la lecture JS).
- Le JavaScript côté client lit cette valeur et l'ajoute dans un header custom (ex:
X-CSRF-Token) à chaque requête. - Le serveur vérifie que la valeur du cookie et la valeur du header correspondent.
Principe de sécurité : un site attaquant ne peut pas lire la valeur du cookie (Same-Origin Policy empêche la lecture cross-origin du cookie depuis le JS de l'attaquant), donc il ne peut pas la reproduire dans le header custom.
Variante renforcée : signer la valeur du cookie avec un HMAC côté serveur pour éviter les attaques par injection de cookie dans certains contextes edge case (subdomain takeover notamment).
5.4 Cookie-to-header pattern pour SPA
Angular, Vue et React implémentent nativement le pattern cookie-to-header avec les cookies XSRF-TOKEN / X-XSRF-TOKEN. Le serveur envoie un cookie non-httpOnly XSRF-TOKEN, le framework SPA le lit et l'injecte en header X-XSRF-TOKEN sur chaque requête HTTP.
Configuration Django + React typique :
# exemple-csrf-django-react.py
# Configuration Django pour SPA React avec cookies CSRF.
# Django gere nativement le token CSRF via son middleware.
# settings.py
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # actif par defaut
# ...
]
# Cookies SameSite et Secure en production
SESSION_COOKIE_SAMESITE = 'Lax'
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = 'Lax'
CSRF_COOKIE_SECURE = True
CSRF_COOKIE_HTTPONLY = False # lisible par le JS pour header injection
CSRF_TRUSTED_ORIGINS = ['https://app.example.test']
# Cote React (axios)
# axios.defaults.xsrfCookieName = 'csrftoken'
# axios.defaults.xsrfHeaderName = 'X-CSRFToken'
# axios.defaults.withCredentials = true5.5 Validation Origin et Referer headers
Défense complémentaire facile à implémenter : vérifier que le header Origin (envoyé automatiquement sur CORS et POST modernes) ou Referer correspond au domaine attendu. Utile en defense-in-depth, pas suffisant seul car le Referer peut être absent sur certaines configurations navigateur (politique no-referrer, proxy strippant les headers).
6. CSRF dans les SPA et APIs modernes
6.1 Le mythe « les SPA n'ont pas besoin de protection CSRF »
Faux. Une SPA est vulnérable à CSRF dès lors que :
- L'authentification passe par un cookie (JWT en cookie httpOnly, session cookie classique).
- L'API accepte des requêtes avec Content-Type standard (
application/x-www-form-urlencoded,multipart/form-data,text/plain). - Les endpoints state-changing ne valident pas une preuve d'origine (token CSRF, Origin header).
Le seul cas où une SPA est structurellement protégée : authentification par token bearer en header (Authorization: Bearer <JWT>) stocké en localStorage ou sessionStorage, jamais envoyé automatiquement par le navigateur. Mais ce choix expose au XSS (localStorage lisible par toute JS injectée), ce qui est un risque souvent pire que CSRF.
6.2 Pattern recommandé 2026
# architecture-spa-api-secure-2026.yml
# Configuration recommandee pour une SPA + API moderne, defense CSRF + XSS.
authentification:
type: "Session cookie OU JWT en cookie httpOnly"
cookie_session:
httpOnly: true
Secure: true
SameSite: "Lax"
Domain: ".example.test"
Path: "/"
Max-Age: 3600
protection_csrf:
pattern: "cookie-to-header (XSRF-TOKEN / X-XSRF-TOKEN)"
cookie_csrf:
httpOnly: false # lisible par JS pour injection header
Secure: true
SameSite: "Lax"
validation_serveur: "comparaison cookie vs header X-CSRF-Token sur chaque requete state-changing"
protection_additionnelle:
origin_validation: "verifier header Origin contre allowlist domaines"
cors:
allow_origins: ["https://app.example.test"]
allow_credentials: true
allow_methods: ["GET", "POST", "PUT", "PATCH", "DELETE"]
allow_headers: ["Content-Type", "X-CSRF-Token"]
max_age: 3600
content_type_strict: "rejet de toute requete POST sans Content-Type: application/json"
monitoring:
log_csrf_failures: "tous les rejets pour token CSRF invalide, investigation sur pics"
rate_limit: "10 requetes state-changing par minute par IP"6.3 Cas GraphQL
Les APIs GraphQL sont particulièrement exposées car elles tunnelisent mutation et query sur un seul endpoint POST. Défenses :
Content-Type: application/jsonobligatoire (rejet strict des autres).- Désactiver explicitement GET pour les mutations (certains serveurs GraphQL l'acceptent par défaut).
- Token CSRF sur les mutations, pas sur les queries read-only.
- CSRF Prevention middleware d'Apollo Server (since v3.7) et graphql-yoga, qui exige un Content-Type application/json ou un header custom.
7. Mythes de prévention à ne pas croire
Cinq « défenses » fréquemment citées qui ne protègent pas contre CSRF :
| Faux ami | Pourquoi ça ne marche pas |
|---|---|
| « Utiliser HTTPS uniquement » | HTTPS chiffre le transport, ne valide pas l'origine de la requête |
| « Vérifier uniquement que l'utilisateur est authentifié » | CSRF exploite précisément une session authentifiée |
| « Utiliser POST au lieu de GET » | POST CSRF via formulaire HTML auto-soumis reste trivial |
| « Cacher l'URL de l'endpoint » | Pas de secret dans un endpoint : il suffit d'inspecter le trafic légitime une fois |
| « Ajouter un captcha sur tout » | Un captcha sur chaque action dégrade l'UX et est contournable via solveurs auto |
8. Détection et test
8.1 Test manuel
Pour un développeur qui audite son code ou un pentester web, protocole de test :
- Inventorier tous les endpoints state-changing (POST, PUT, PATCH, DELETE, et les GET suspects).
- Identifier la présence ou l'absence d'un token CSRF dans chaque requête (Burp Suite met cela en évidence automatiquement via son CSRF detection).
- Rejouer la requête depuis une page HTML externe minimale (formulaire auto-soumis hébergé sur un domaine différent).
- Tester les bypasses : token vide, token absent, token d'une autre session, token réutilisé, changement de méthode HTTP (POST → PUT avec override via
X-HTTP-Method-Override). - Vérifier les headers SameSite sur les cookies de session via DevTools → Application → Cookies.
8.2 Labs d'entraînement
PortSwigger Web Security Academy – CSRF propose 10 labs gratuits couvrant : CSRF sans défense, token absent, token non validé, token pas lié à la session, token dupliqué en cookie, referer non validé, référer validé mais contournable, SameSite strict bypass, SameSite lax bypass via GET state-changing, XSS → CSRF. Entraînement de référence pour maîtriser le sujet en 1-2 semaines.
8.3 CVE CSRF récentes
CSRF reste présente en 2026 malgré SameSite par défaut :
| CVE | Produit | Cause technique |
|---|---|---|
| CVE-2026-40925 | WWBN AVideo | SameSite=None pour iframe embed, POST auto-submit cross-origin |
| CVE-2026-39371 | RedwoodJS SDK | GET state-changing avec SameSite=Lax, CSRF via navigation top-level |
| CVE-2026-29084 | Gokapi | Absence de validation CSRF sur endpoints admin |
| CVE-2026-3589 | WooCommerce plugin | Token CSRF non validé sur action d'import |
| CVE-2026-28281 | InstantSoft icms2 | CSRF sur modification de configuration admin |
Pour approfondir les catégories OWASP voisines de CSRF, voir la ressource vulnérabilité XSS expliquée (XSS peut contourner les protections CSRF), injection SQL expliquée pour la grande famille d'injections OWASP A03, introduction OWASP Top 10 pour la vue d'ensemble technique, et OWASP Top 10 expliqué simplement pour une version pédagogique des 10 catégories.
Points clés à retenir
- CSRF = requête authentifiée forgée depuis une origine tierce, exploite l'ambient authority des cookies, CWE-352, #3 CWE Top 25 2025.
- Mécanisme : le navigateur envoie automatiquement les cookies à leur domaine propriétaire, indépendamment de qui initie la requête.
- Variantes : GET-based (pire cas design), POST-based (classique), JSON CSRF (via Content-Type bypass), login CSRF (compte attaquant poussé à la victime).
- Défense #1 2026 : SameSite=Lax par défaut (enforcé Chromium/Firefox/Safari depuis 2020-2021), ne suffit pas seul.
- Stateful apps : synchronizer token pattern avec middleware framework (Django, Rails, Laravel, csrf-sync pour Express).
- Stateless apps / SPA : double submit cookie ou cookie-to-header pattern (XSRF-TOKEN / X-XSRF-TOKEN).
- Defense-in-depth : validation Origin header + Content-Type strict + rate limiting + logging des rejets.
- Mythe dangereux : « les SPA n'ont pas besoin de protection CSRF ». Faux dès que l'auth passe par un cookie.
- XSS cassent toutes les défenses CSRF : les deux vulnérabilités doivent être traitées ensemble, jamais séparément.
- Référentiel : OWASP CSRF Prevention Cheat Sheet (maintenu à jour par la communauté OWASP).







