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
Referer
etOrigin
pour 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
POST
et non auxGET
. - Utiliser
SameSite
pour les cookies : Configurer les cookies avecSameSite=Strict
ouSameSite=Lax
pour é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 - Création du serveur local
Si vous utilisez XAMPP ou WAMP, démarrez Apache et MySQL.
Sinon, utilisez PHP en ligne de commande :
php -S localhost:8000
Placez tous les fichiers du TP dans un dossier nommé csrf_tp
et exécutez-les depuis votre navigateur à l’adresse http://localhost/csrf_tp/
.
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/csrf_tp/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.php
dans votre navigateur et assurez-vous que l’email affiché est correct. - Ouvrez
attack.html
dans un autre onglet. - Retournez sur
index.php
et 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.php
et notez le jeton CSRF dans le code source (Ctrl + U
). - Essayez de soumettre
attack.html
à nouveau. - Retournez sur
index.php
et 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']);