Skip to content

CSRF : Cross-Site Request Forgery

1️⃣ Génération du token

On génère le token lors de l’affichage du formulaire.

On stocke dans la session :

  • le token lui-même
  • sa date/heure de création

Exemple en PHP :

function generateCsrfToken() {
    $token = bin2hex(random_bytes(32));
    $_SESSION['csrf_token'] = $token;
    $_SESSION['csrf_token_time'] = time(); // enregistre l'heure
    return $token;
}

2️⃣ Ajout dans le formulaire

<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(generateCsrfToken()); ?>">

3️⃣ Vérification avec expiration

On définit une durée de vie (par exemple 900 secondes = 15 min).

function checkCsrfToken($token) {
    $maxLifetime = 900; // 15 minutes
    if (!isset($_SESSION['csrf_token'], $_SESSION['csrf_token_time'])) {
        return false; // pas de token en session → invalide
    }
    if (time() - $_SESSION['csrf_token_time'] > $maxLifetime) {
        return false; // expiré
    }
    if (!hash_equals($_SESSION['csrf_token'], $token)) {
        return false; // valeur incorrecte
    }
    return true;
}

4️⃣ Révocation après usage

Pour éviter la réutilisation (attaque par “replay”), on détruit le token une fois validé :

unset($_SESSION['csrf_token'], $_SESSION['csrf_token_time']);

📌 Conseils sécurité

  • Durée courte : 5 à 15 min selon le contexte (plus court = plus sûr, mais plus de formulaires qui expirent).

  • Token par formulaire ou par action : plus strict, mais plus compliqué à gérer.

  • Renouvellement automatique : régénérer le token à chaque affichage de formulaire.

  • HTTPS obligatoire : un CSRF ne se base pas sur le vol de token, mais autant empêcher toute interception.

Exemple complet

csrf.php (à inclure dans les pages)

<?php
session_start();

/**
 * Génère un token CSRF avec nom de champ dynamique
 * @param int $lifetime Durée de vie en secondes
 * @return array [ 'name' => string, 'token' => string ]
 */
function generateCsrfToken($lifetime = 600) {
    // Nom dynamique, ex: CSRF_ab12cd
    $name = 'CSRF_' . bin2hex(random_bytes(3));
    $token = bin2hex(random_bytes(32));

    // Stocke dans la session
    $_SESSION['csrf'][$name] = [
        'token' => $token,
        'time'  => time(),
        'ttl'   => $lifetime
    ];

    return [ 'name' => $name, 'token' => $token ];
}

/**
 * Vérifie le token CSRF et supprime après usage
 */
function checkCsrfToken() {
    foreach ($_POST as $fieldName => $value) {
        if (strpos($fieldName, 'CSRF_') === 0) {
            if (isset($_SESSION['csrf'][$fieldName])) {
                $data = $_SESSION['csrf'][$fieldName];

                // Expiration
                if (time() - $data['time'] > $data['ttl']) {
                    unset($_SESSION['csrf'][$fieldName]);
                    return false; // expiré
                }

                // Correspondance du token
                if (hash_equals($data['token'], $value)) {
                    unset($_SESSION['csrf'][$fieldName]); // usage unique
                    return true;
                }
            }
        }
    }
    return false; // pas trouvé ou invalide
}

formulaire.php

<?php
require 'csrf.php';

// Génère le token
$csrf = generateCsrfToken(600); // 10 min

?>
<!DOCTYPE html>
<html>
<head><meta charset="UTF-8"><title>Formulaire sécurisé</title></head>
<body>
<form method="post" action="traitement.php">
    <input type="hidden" name="<?= htmlspecialchars($csrf['name']) ?>" value="<?= htmlspecialchars($csrf['token']) ?>">
    
    Nom : <input type="text" name="nom"><br>
    Message : <input type="text" name="message"><br>
    <button type="submit">Envoyer</button>
</form>
</body>
</html>

traitement.php

<?php
require 'csrf.php';

if (!checkCsrfToken()) {
    die("❌ CSRF invalide ou expiré");
}

$nom = htmlspecialchars($_POST['nom'] ?? '');
$message = htmlspecialchars($_POST['message'] ?? '');

echo "✅ Formulaire reçu !<br>";
echo "Nom : $nom<br>";
echo "Message : $message";

🔒 Points forts de cet exemple :

  • Nom dynamique du champ (CSRF_xxxxx) comme OCS → plus difficile à cibler.
  • Durée de vie courte (10 min configurable).
  • Usage unique : le token est supprimé après validation.
  • Stockage structuré en session ($_SESSION['csrf'] multi-tokens possible si plusieurs formulaires affichés).
Edited by Gabriel Moreau