Aller au contenu principal

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é.

remarque

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 ?

  1. L’utilisateur est connecté à un site légitime (exemple : une banque en ligne).
  2. L’attaquant lui envoie un lien malveillant (par email, message ou site tiers).
  3. Si l’utilisateur clique sur le lien, une requête est envoyée au site légitime sans son consentement.
  4. 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 et Origin 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 aux GET.
  • Utiliser SameSite pour les cookies : Configurer les cookies avec SameSite=Strict ou SameSite=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>
astuce

Avec cette protection, une attaque CSRF échouera car l’attaquant ne connaît pas le jeton CSRF.


Test de mémorisation/compréhension


Quel est le but principal d'une attaque CSRF ?


Pourquoi les cookies sont-ils vulnérables au CSRF ?


Quel type de requête est souvent utilisé dans une attaque CSRF ?


Quelle est la meilleure protection contre le CSRF ?


Comment un attaquant incite-t-il un utilisateur à exécuter une action CSRF ?


Pourquoi vérifier l'en-tête 'Referer' peut aider contre le CSRF ?


Que fait l'option 'SameSite' pour les cookies ?


Quel est le risque si un formulaire n'a pas de protection CSRF ?


Pourquoi un jeton CSRF est efficace ?


Quel est le principal défaut du CSRF ?


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
  1. Ouvrez index.php dans votre navigateur et assurez-vous que l’email affiché est correct.
  2. Ouvrez attack.html dans un autre onglet.
  3. 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
  1. Rechargez index.php et notez le jeton CSRF dans le code source (Ctrl + U).
  2. Essayez de soumettre attack.html à nouveau.
  3. 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']);