CSRF : Cross-Site Request Forgery
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;
}
<input type="hidden" name="csrf_token" value="<?= htmlspecialchars(generateCsrfToken()); ?>">
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;
}
Pour éviter la réutilisation (attaque par “replay”), on détruit le token une fois validé :
unset($_SESSION['csrf_token'], $_SESSION['csrf_token_time']);
-
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";
- 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).