CSRF
Cross-Site Request Forgery (CSRF) : Protéger les sessions utilisateur
Notions théoriques
Le Cross-Site Request Forgery (CSRF) est une attaque qui force un utilisateur connecté à exécuter une action non désirée sur un site Web où il est authentifié.
Contrairement au Cross-Site Scripting (XSS), qui permet d'injecter du code malveillant, le CSRF exploite la confiance d'un site dans les requêtes authentifiées par l'utilisateur.
Comment fonctionne une attaque CSRF ?
- L’utilisateur est connecté à un site légitime (exemple : une banque en ligne).
- L’attaquant lui envoie un lien malveillant (par email, message ou site tiers).
- Si l’utilisateur clique sur le lien, une requête est envoyée au site légitime sans son consentement.
- Le site exécute la requête avec les droits de l’utilisateur, car son cookie de session est automatiquement inclus.
Exemple d'attaque CSRF
Un site bancaire permet de transférer de l'argent via une requête POST :
<form action="https://ma-banque.com/virement" method="POST">
    <input type="hidden" name="destinataire" value="attacker123">
    <input type="hidden" name="montant" value="1000">
    <input type="submit" value="Envoyer">
</form>
Si un utilisateur connecté à sa banque clique sur un lien contenant ce formulaire, 1000€ seront envoyés à l'attaquant sans qu'il s'en rende compte.
Les cookies sont vulnérables
Les navigateurs envoient automatiquement les cookies de session avec chaque requête HTTP vers un site web.
Cela signifie qu'une requête malveillante peut être exécutée sans que l’utilisateur ait à saisir ses identifiants.
Comment se protéger contre le CSRF ?
- Utiliser des jetons CSRF (CSRF Token) : Un jeton unique est généré pour chaque formulaire et vérifié par le serveur.
- Vérifier l'origine des requêtes : Vérifier les en-têtes RefereretOriginpour s'assurer que la requête provient du bon site.
- Désactiver les requêtes non sécurisées : Restreindre certaines actions aux requêtes POSTet non auxGET.
- Utiliser SameSitepour les cookies : Configurer les cookies avecSameSite=StrictouSameSite=Laxpour éviter leur envoi sur des requêtes intersites.
Exemple pratique
Exploiter une faille CSRF
Un site propose une fonctionnalité de changement d'email :
<?php
session_start();
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    $nouvel_email = $_POST['email'];
    // Mise à jour de l'email sans vérification CSRF
    echo "Votre email a été mis à jour : " . htmlspecialchars($nouvel_email);
}
?>
<form method="POST">
    <input type="email" name="email" required>
    <button type="submit">Changer d'email</button>
</form>
Si un utilisateur connecté visite une page malveillante contenant :
<img src="http://site-legitime.com/changer_email.php?email=hacker@mail.com">
Son email sera changé sans son consentement.
Correction avec un jeton CSRF
Ajout d’un CSRF Token pour sécuriser la requête :
<?php
session_start();
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
        die("Échec de vérification CSRF");
    }
    $nouvel_email = $_POST['email'];
    echo "Votre email a été mis à jour : " . htmlspecialchars($nouvel_email);
}
?>
<form method="POST">
    <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
    <input type="email" name="email" required>
    <button type="submit">Changer d'email</button>
</form>
Avec cette protection, une attaque CSRF échouera car l’attaquant ne connaît pas le jeton CSRF.
Test de mémorisation/compréhension
TP pour réfléchir et résoudre des problèmes
Comprendre et corriger une faille CSRF
Étape 1 : Mise en place d'un site vulnérable au CSRF
Dans cette première partie, nous allons créer une page PHP qui permet à un utilisateur de modifier son adresse email sans protection contre le CSRF.
1.1 - Démarrage du serveur local
En ligne de commande :
php -S localhost:8000
Placez tous les fichiers du TP dans un dossier nommé
tp_csrfet exécutez-les depuis votre navigateur à l’adressehttp://localhost:8000/.
1.2 - Création du fichier index.php
Ce fichier simule un site où un utilisateur peut modifier son adresse email.
Créez un fichier index.php et ajoutez le code suivant :
<?php
session_start();
// Simulation d'un utilisateur connecté
if (!isset($_SESSION['email'])) {
    $_SESSION['email'] = "user@example.com";
}
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // Mise à jour de l'email sans protection CSRF
    $_SESSION['email'] = $_POST['email'];
    echo "<p style='color:green;'>Votre email a été mis à jour : " . htmlspecialchars($_SESSION['email']) . "</p>";
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Modification d'email</title>
</head>
<body>
    <h2>Modifier votre email</h2>
    <p>Email actuel : <strong><?php echo htmlspecialchars($_SESSION['email']); ?></strong></p>
    <form method="POST">
        <label>Nouvel email :</label>
        <input type="email" name="email" required>
        <button type="submit">Mettre à jour</button>
    </form>
</body>
</html>
✔️ Testez le formulaire en modifiant l'email. Vous verrez qu'il est mis à jour sans aucune protection.
Étape 2 : Exploitation de la faille CSRF
Nous allons maintenant créer une page malveillante qui envoie une requête à index.php sans que l'utilisateur ne s'en rende compte.
2.1 - Création du fichier attack.html
Créez un fichier attack.html et ajoutez le code suivant :
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Site Malveillant</title>
</head>
<body>
    <h2>Bienvenue sur mon site !</h2>
    <p>Regardez cette belle image :</p>
    
    <!-- Formulaire caché qui envoie une requête automatique -->
    <form action="http://localhost:8000/index.php" method="POST" id="csrfForm">
        <input type="hidden" name="email" value="hacker@evil.com">
    </form>
    <script>
        document.getElementById("csrfForm").submit();
    </script>
</body>
</html>
2.2 - Exécution de l'attaque
- Ouvrez index.phpdans votre navigateur et assurez-vous que l’email affiché est correct.
- Ouvrez attack.htmldans un autre onglet.
- Retournez sur index.phpet actualisez la page.
💀 L’email a été changé à votre insu ! L’attaque CSRF a réussi.
Étape 3 : Sécurisation avec un Jeton CSRF
Nous allons maintenant corriger la faille CSRF en ajoutant un jeton unique à chaque requête.
3.1 - Mise à jour de index.php**
Modifiez index.php pour inclure un jeton CSRF :
<?php
session_start();
// Simulation d'un utilisateur connecté
if (!isset($_SESSION['email'])) {
    $_SESSION['email'] = "user@example.com";
}
// Génération du jeton CSRF s'il n'existe pas
if (!isset($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
if ($_SERVER["REQUEST_METHOD"] == "POST") {
    // Vérification du jeton CSRF
    if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
        die("<p style='color:red;'>Échec de vérification CSRF !</p>");
    }
    
    // Mise à jour de l'email
    $_SESSION['email'] = $_POST['email'];
    echo "<p style='color:green;'>Votre email a été mis à jour : " . htmlspecialchars($_SESSION['email']) . "</p>";
}
?>
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>Modification d'email</title>
</head>
<body>
    <h2>Modifier votre email</h2>
    <p>Email actuel : <strong><?php echo htmlspecialchars($_SESSION['email']); ?></strong></p>
    <form method="POST">
        <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
        <label>Nouvel email :</label>
        <input type="email" name="email" required>
        <button type="submit">Mettre à jour</button>
    </form>
</body>
</html>
3.2 - Test de la protection
- Rechargez index.phpet notez le jeton CSRF dans le code source (Ctrl + U).
- Essayez de soumettre attack.htmlà nouveau.
- Retournez sur index.phpet vérifiez si l’attaque a réussi.
✔️ L’attaque échoue maintenant, car le jeton CSRF est manquant ou incorrect.
Étape 4 : Aller plus loin
4.1 - Vérification de l'origine des requêtes**
Ajoutez une vérification des en-têtes HTTP Referer et Origin :
if (!empty($_SERVER['HTTP_ORIGIN']) && parse_url($_SERVER['HTTP_ORIGIN'], PHP_URL_HOST) !== "localhost") {
    die("<p style='color:red;'>Requête interdite !</p>");
}
4.2 - Utilisation des cookies SameSite
Dans votre configuration PHP, ajoutez :
session_set_cookie_params(['samesite' => 'Strict']);
Afficher le CSRF token de Symfony
Dans les versions récentes de Symfony (notamment à partir de Symfony 7), le token CSRF s'affiche littéralement comme "csrf-token" au lieu d'une valeur réelle.
C'est la configuration par défaut qui active la protection CSRF stateless (sans état, sans session), ce qui n'est pas le comportement des formulaires Symfony traditionnels.
Dans votre projet, un fichier config/packages/csrf.yaml est créé automatiquement
et configure la protection CSRF comme stateless pour certaines intentions, y compris 'authenticate'.
Cela fait que la fonction Twig {{ csrf_token('authenticate') }} renvoie un placeholder "csrf-token"
au lieu d'un token généré dynamiquement via le gestionnaire de sessions.
Pour voir le "csrf-token" dans le code source HTML de la page de login
- 
Localisez et modifiez le fichier csrf.yaml:- 
Ouvrez config/packages/csrf.yaml.
- 
Vous devriez voir quelque chose comme ça : framework:
 form:
 csrf_protection:
 token_id: submit
 csrf_protection:
 stateless_token_ids:
 - submit
 - authenticate # <-- Cette ligne correspond à la page de login
 - logout
- 
Commentez 'authenticate'de la listestateless_token_ids:framework:
 csrf_protection:
 stateless_token_ids:
 - submit
 # - authenticate # Commenté pour restaurer le token stateful de la page de login
 - logout
 
- 
- 
Videz les caches : - Exécutez php bin/console cache:clear(ousymfony cache:clearsi vous utilisez Symfony CLI).
- Ou supprimez manuellement le dossier var/cache/et relancez le serveur.
 
- Exécutez