CSRF expliqué simplement
Le CSRF (Cross-Site Request Forgery) exploite le fait que le navigateur envoie automatiquement les cookies de session avec chaque requête, même déclenchée depuis un site tiers. Résultat : un site malveillant peut faire exécuter une action à un utilisateur authentifié, sans qu’il s’en rende compte.
Sommaire
Le problème #
Imaginez un utilisateur connecté sur le site de sa banque, dans un onglet. Dans un autre onglet, il visite un site quelconque, qui contient (à son insu) un formulaire caché déclenchant automatiquement une requête vers banque.test/virement?montant=5000&vers=attaquant. Le navigateur envoie cette requête avec les cookies de session de la banque, parce que ces cookies sont associés au domaine banque.test, peu importe quel site a initié la requête.
Si le serveur de la banque ne vérifie que la présence d’une session valide — sans vérifier que la requête provient réellement d’une action volontaire de l’utilisateur sur son propre site — il exécute le virement. L’utilisateur n’a rien cliqué de suspect, n’a rien téléchargé, n’a même pas conscience qu’une requête est partie en son nom.
L’idée générale #
Le CSRF (Cross-Site Request Forgery, littéralement “falsification de requête intersite”) exploite un fait technique simple : les cookies sont automatiquement joints par le navigateur à chaque requête vers le domaine auquel ils appartiennent, sans considération de la page qui a déclenché cette requête.
Contrairement à l’injection SQL ou au XSS, le CSRF n’injecte aucun code et ne vole aucune donnée directement. Il détourne une action légitime que l’application autoriserait de toute façon si l’utilisateur l’avait déclenchée volontairement — changer un email, poster un message, valider un virement, supprimer un compte.
La protection standard consiste à ajouter, à chaque action sensible, une preuve que la requête provient bien du site lui-même et non d’un tiers : le token CSRF. C’est une valeur générée côté serveur, imprévisible, incluse dans chaque formulaire, et vérifiée à la réception de la requête. Un site tiers peut forcer l’envoi d’une requête avec les bons cookies, mais il ne peut pas deviner ni lire ce token — les cookies d’un domaine ne sont jamais accessibles en lecture par un autre domaine.
Analogie du quotidien #
Le CSRF, c’est comme un chèque déjà signé qui traîne dans votre portefeuille. Le simple fait de le sortir et de le présenter suffit à ce qu’il soit encaissé — la banque ne vérifie que la signature, pas qui a rempli le montant ni qui l’a physiquement déposé au guichet. Si quelqu’un glisse subrepticement un chèque pré-rempli dans votre portefeuille et le fait présenter à votre insu, la signature (votre cookie de session, toujours valide) suffit à ce que l’opération passe.
Le token CSRF, c’est l’équivalent d’un code à usage unique que la banque exige en plus de la signature, un code que seul le vrai propriétaire du chéquier connaît au moment de remplir le chèque — impossible pour quelqu’un d’extérieur de le deviner ou de le glisser dans le portefeuille à votre place.
Diagramme #
Exemple de code #
Code vulnérable #
<?php
// traitement-virement.php : aucune vérification autre que la session
session_start();
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
exit;
}
$montant = $_POST['montant'];
$destinataire = $_POST['vers'];
effectuerVirement($_SESSION['user_id'], $destinataire, $montant);
<!-- formulaire-virement.php : aucun token -->
<form method="POST" action="/traitement-virement.php">
<input type="number" name="montant">
<input type="text" name="vers">
<button type="submit">Virer</button>
</form>
Ce formulaire peut être reproduit à l’identique sur n’importe quel autre site : le navigateur de la victime enverra quand même le cookie de session valide.
Code corrigé #
<?php
// formulaire-virement.php : génération d'un token lié à la session
session_start();
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
$csrfToken = $_SESSION['csrf_token'];
?>
<form method="POST" action="/traitement-virement.php">
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars($csrfToken) ?>">
<input type="number" name="montant">
<input type="text" name="vers">
<button type="submit">Virer</button>
</form>
<?php
// traitement-virement.php : vérification stricte du token avant d'agir
session_start();
if (!isset($_SESSION['user_id'])) {
http_response_code(401);
exit;
}
$tokenRecu = $_POST['csrf_token'] ?? '';
$tokenAttendu = $_SESSION['csrf_token'] ?? '';
if (!hash_equals($tokenAttendu, $tokenRecu)) {
http_response_code(403);
exit('Requête refusée : jeton CSRF invalide.');
}
$montant = $_POST['montant'];
$destinataire = $_POST['vers'];
effectuerVirement($_SESSION['user_id'], $destinataire, $montant);
hash_equals() compare les deux chaînes en temps constant, pour éviter qu’un attaquant ne devine le token par une attaque de type timing.
Quand s’en préoccuper ? #
- Sur toute action qui modifie un état côté serveur : changement de mot de passe ou d’email, virement, suppression de compte, publication de contenu — dès qu’une requête
POST,PUT,PATCHouDELETEa un effet de bord. - Moins critique sur les requêtes
GETen lecture seule, à condition qu’elles ne déclenchent réellement aucune modification côté serveur (une requêteGETqui modifie des données est de toute façon une mauvaise pratique en soi, indépendamment du CSRF). - Sur les applications qui exposent une API consommée par un frontend séparé : la protection prend alors une forme différente (vérification de l’en-tête
Origin/Referer, tokens dans un en-tête personnalisé plutôt qu’un champ de formulaire), car les cookies de session classiques sont parfois remplacés par des tokens d’authentification transmis explicitement. - Dès la mise en place de l’authentification d’un projet : la plupart des frameworks PHP modernes (Symfony, Laravel) génèrent et vérifient les tokens CSRF automatiquement — il faut surtout veiller à ne pas désactiver cette protection par erreur ou par gain de temps.
Points importants #
- L’attribut de cookie
SameSite(àLaxouStrict) est une deuxième ligne de défense efficace : il empêche le navigateur d’envoyer le cookie de session lors d’une requête initiée depuis un autre site, dans la majorité des cas. Il complète le token CSRF, mais ne le remplace pas entièrement selon le contexte. - Un token CSRF doit être généré avec une source aléatoire cryptographiquement sûre (
random_bytes()en PHP), jamais avecrand()ouuniqid(), qui sont prévisibles. - La comparaison du token reçu doit toujours utiliser une fonction en temps constant (
hash_equals()), pas l’opérateur==ou===, pour éviter les attaques par mesure de temps. - CSRF et XSS sont souvent confondus car tous deux impliquent un site tiers ou un contenu injecté, mais leurs mécanismes et leurs protections sont complètement différents : l’un force une action à l’insu de la victime, l’autre exécute du code dans son navigateur.
🐻 À retenir
- ●Le CSRF ne vole aucune donnée et ne casse aucun mot de passe : il abuse d’une action déjà autorisée en la déclenchant à l’insu de la victime.
- ●La faille repose sur un fait technique précis : le navigateur envoie automatiquement les cookies de session, même pour une requête initiée depuis un site totalement différent.
- ●La protection standard est le token CSRF : une valeur imprévisible, unique par session (ou par formulaire), vérifiée côté serveur avant d’exécuter toute action sensible.