CSRF et CORS s’attaquent à deux facettes distinctes de la Same-Origin Policy : la première exploite ce que le navigateur autorise en envoi malgré la SOP, la seconde formalise ce qu’il autorise en lecture lorsqu’on l’assouplit délibérément.

CSRF : forger une requête au nom de la victime

Le modèle historique, avant SameSite

Avant l’attribut SameSite, le navigateur pouvait charger n’importe quelle ressource cross-site — via un formulaire, une image, un script, une iframe — en y joignant automatiquement les cookies du site cible. Le serveur, de son côté, ne pouvait pas distinguer une requête légitimement initiée depuis son propre site d’une requête forgée par un site malveillant qui contraignait la victime à l’émettre. Ce terrain était idéal pour le CSRF.

Le principe de l’attaque

Idée générale

Forcer la victime, à son insu, à envoyer une requête authentifiée vers une application où elle est déjà connectée.

L’exemple canonique se déroule en quatre temps. L’utilisateur est connecté sur bank.example.com. Il visite ensuite evil.com, qui contient :

<form action="https://bank.example.com/transfer" method="POST">
  <input type="hidden" name="to" value="attacker" />
  <input type="hidden" name="amount" value="5000" />
</form>
<script>
  document.forms[0].submit();
</script>

Le navigateur ajoute automatiquement le cookie de session de la banque à cette requête POST — la banque, recevant une requête authentifiée en bonne et due forme, croit que l’utilisateur a validé le virement lui-même.

Les défenses modernes

Trois mécanismes se combinent, rarement de façon exclusive.

Le jeton CSRF (synchronizer token) reste la référence : un champ caché, présent dans chaque formulaire, dont la valeur aléatoire est propre à la session et vérifiée côté serveur. Un attaquant, ignorant cette valeur, ne peut pas la reproduire dans sa requête forgée.

La vérification d’Origin ou de Referer complète ce jeton : pour toute requête POST sensible, le serveur refuse si l’en-tête Origin ne correspond pas exactement au domaine attendu.

Les cookies SameSite déplacent enfin une part de la responsabilité vers le navigateur lui-même : SameSite=Lax protège contre la plupart des CSRF silencieux, SameSite=Strict isole totalement, SameSite=None; Secure réautorise explicitement les cookies cross-site pour les cas légitimes (SSO, iframes).

NOTE

Même avec SameSite=Lax activé, les jetons CSRF restent indispensables pour toute opération critique — la protection du navigateur est une ligne de défense supplémentaire, jamais un substitut à la vérification côté serveur.

CORS : formaliser ce que la SOP interdisait

Rappel : ce que la SOP bloquait

La Same-Origin Policy interdit la lecture des réponses XHR/fetch cross-origin — mais pas leur envoi. Pour les API REST modernes, consommées légitimement depuis d’autres origines, cette rigidité devenait un obstacle : il fallait un mécanisme pour l’assouplir explicitement, sous le contrôle du serveur. C’est le rôle de CORS.

Le mécanisme

Lorsqu’un script effectue fetch("https://api.example.com/data", { credentials: "include" }), le navigateur ajoute un en-tête Origin: https://app.example.com à la requête. Le serveur répond, s’il l’autorise, avec Access-Control-Allow-Origin: https://app.example.com et, le cas échéant, Access-Control-Allow-Credentials: true. C’est ensuite le navigateur — jamais le serveur — qui décide, à réception de cette réponse, s’il laisse le script JavaScript lire le résultat, ou s’il le bloque tout en laissant la requête avoir eu lieu côté serveur.

Ce que CORS protège, et ce qu'il ne protège pas

CORS protège l’utilisateur contre la lecture non autorisée d’une réponse par un script tiers — il ne protège en rien l’API elle-même. Authentification et autorisation restent entièrement à la charge du serveur ; CORS ne les remplace jamais.

Les erreurs de configuration classiques

Deux fautes reviennent systématiquement. Refléter l’origine reçue sans discernement — répondre Access-Control-Allow-Origin: <Origin reçu> combiné à Access-Control-Allow-Credentials: true — revient à autoriser tout le web à lire les réponses authentifiées. Une liste blanche mal conçue, du type if "example.com" in Origin: allow, se laisse contourner trivialement par un domaine comme evil-example.com, qui contient bien la sous-chaîne attendue.

La conséquence est directe : un site malveillant peut alors envoyer une requête XHR avec les cookies de la victime et lire la réponse — la SOP est cassée, et c’est un vol de données cross-site pur et simple.

Une configuration CORS stricte

La discipline à tenir : une liste blanche précise et exhaustive des origines autorisées, jamais de caractère générique * combiné à credentials: true, un refus par défaut, et l’exclusion systématique de toute correspondance par sous-chaîne ou expression régulière mal maîtrisée.