CSRF (Cross-Site Request Forgery) en PHP
Le CSRF (Cross-Site Request Forgery) est une attaque qui force un utilisateur authentifié à exécuter des actions non désirées sur une application web, sans son consentement.
- Mécanisme :
Un site malveillant (ou un email piégé) envoie une requête HTTP à l’insu de la victime vers un site vulnérable (ex:
bank.com/transfer?to=hacker&amount=1000), en exploitant :- Les cookies de session déjà actifs (l’utilisateur est connecté).
- L’absence de vérification de l’origine de la requête.
- Impact :
- Modification de données (ex: changement de mot de passe, transfert d’argent).
- Actions administratives (suppression de compte, envoi de messages).
- Exemple concret :
Une victime connectée à
facebook.comclique sur un lien malveillant qui envoie une requête POST pour publier un message à son insu ou ajouter un ami frauduleux.
⚠️ Pourquoi c’est dangereux ? Contrairement au XSS (qui vole des données), le CSRF exploite la confiance du serveur envers l’utilisateur authentifié.
Prompt pour l'IA
Analyse ce code PHP pour détecter des vulnérabilités Cross-Site Request Forgery (CSRF). Vérifie les points suivants :
1. Absence de tokens CSRF
- Critères :
- Les formulaires (GET/POST) ou requêtes sensibles (modification de données, suppression, transactions) n’utilisent pas de token CSRF.
- Recherche de motifs dangereux :
// ❌ Vulnérable : Aucun token CSRF <form action="transfer.php" method="POST"> <input type="hidden" name="amount" value="1000"> <input type="submit" value="Envoyer"> </form> - Vérifie l’absence de :
csrf_tokendans les formulaires.- Fonctions de génération de token (
bin2hex(random_bytes(32)),hash_hmac, ou librairies commeSymfony/CSRF).
2. Méthodes HTTP non protégées
- Requêtes GET pour des actions sensibles :
- Exemple de code dangereux :
// ❌ GET pour une action critique (doit être POST + token) if ($_GET['action'] === 'delete_account') { deleteUserAccount($_GET['user_id']); }
- Exemple de code dangereux :
- En-têtes
Referer/Originnon vérifiés :- Le code ne vérifie pas si la requête vient bien du même domaine (
$_SERVER['HTTP_REFERER']ou$_SERVER['HTTP_ORIGIN']).
- Le code ne vérifie pas si la requête vient bien du même domaine (
3. Mauvaise gestion des cookies
- Cookies sans attributs
SameSite:- Recherche de
setcookie()sans :setcookie("session", $token, [ 'samesite' => 'Strict', // ou 'Lax' 'secure' => true, // HTTPS obligatoire 'httponly' => true // Empêche l'accès via JavaScript ]);
- Recherche de
- Sessions vulnérables :
- Utilisation de
session_start()sans régénération de l’ID de session après login (session_regenerate_id(true)).
- Utilisation de
4. Fausses protections
- Tokens CSRF statiques ou prévisibles :
- Exemple de code inefficace :
// ❌ Token prévisible (basé sur l'IP ou un timestamp) $csrf_token = md5($_SERVER['REMOTE_ADDR'] . time()); - Les tokens doivent être uniques par session et par formulaire, stockés côté serveur (ex: en
$_SESSION).
- Exemple de code inefficace :
Question bonus pour l’IA :
"Propose des correctifs pour chaque vulnérabilité identifiée, en utilisant les bonnes pratiques PHP modernes (ex: librairies comme paragonie/csrf-magic ou les composants Symfony)."
Explication détaillée
🔍 Comment fonctionne une attaque CSRF ?
Scénario type :
- La victime est connectée à
vulnerable-site.com(son cookie de session est actif). - Elle visite un site malveillant (ou clique sur un lien dans un email) contenant :
<!-- Code HTML malveillant (ex: dans une iframe invisible) --> <img src="https://vulnerable-site.com/change-email?new_email=hacker@evil.com" width="0" height="0"> - Le navigateur envoie automatiquement la requête avec le cookie de session de la victime.
- Le serveur exécute l’action sans vérifier l’origine de la requête.
Pourquoi ça marche ?
- Les cookies sont envoyés automatiquement par le navigateur pour les requêtes vers le même domaine.
- Le serveur ne fait pas la différence entre une requête légitime et une requête forcée.
🛡️ Solutions pour se protéger en PHP
1. Utiliser des tokens CSRF
Principe : Ajouter un token unique et imprévisible à chaque formulaire, et le vérifier côté serveur.
Exemple avec PHP natif :
// Génération du token (à faire une fois par session)
if (empty($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// Dans le formulaire HTML
<form action="transfer.php" method="POST">
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
<input type="text" name="amount">
<input type="submit" value="Envoyer">
</form>
// Vérification côté serveur
if ($_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die("CSRF token invalid!");
}
Avec une librairie (recommandé) :
// Utilisation de paragonie/csrf-magic (composer require paragonie/csrf-magic)
require 'vendor/autoload.php';
$csrf = new \ParagonIE\AntiCSRF\AntiCSRF;
// Dans le formulaire
<input type="hidden" name="csrf_token" value="<?php echo $csrf->insertToken(); ?>">
// Vérification
try {
$csrf->validateRequest();
} catch (\ParagonIE\AntiCSRF\AntiCSRFException $e) {
die("CSRF validation failed!");
}
2. Vérifier l’en-tête Referer ou Origin
Limite : Peut être contourné si l’attaquant utilise un sous-domaine ou un protocole différent (HTTP vs HTTPS).
$referer = $_SERVER['HTTP_REFERER'] ?? '';
$allowed_domain = 'https://votre-site.com';
if (strpos($referer, $allowed_domain) !== 0) {
die("Requête non autorisée (mauvais Referer).");
}
3. Configurer les cookies avec SameSite
Solution la plus simple (mais nécessite HTTPS) :
// Dans php.ini ou via .htaccess
session.cookie_samesite = "Strict" // ou "Lax"
// Ou en PHP
ini_set('session.cookie_samesite', 'Strict');
session_start();
Strict: Le cookie n’est envoyé que pour les requêtes vers le même site (idéal pour les zones sensibles).Lax: Autorise les liens externes (ex: depuis un email), mais bloque les requêtes POST cross-site.
4. Privilégier les requêtes POST pour les actions sensibles
Règle d’or :
- GET = Récupération de données (idempotent).
- POST = Modification de données (doit être protégé par CSRF).
Exemple de middleware PHP pour forcer POST :
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
die("Méthode non autorisée.");
}
5. Utiliser des frameworks modernes
Les frameworks PHP populaires intègrent déjà des protections CSRF :
- Laravel : Token CSRF automatique via
@csrfdans les formulaires. - Symfony : Composant
Security/CSRFaveccsrf_token(). - WordPress : Fonction
wp_nonce_field().
Exemple avec Laravel :
// Dans un formulaire Blade
<form method="POST" action="/transfer">
@csrf <!-- Génère un token CSRF -->
<input type="text" name="amount">
</form>
🎯 Bonnes pratiques supplémentaires
| Pratique | Exemple de code | Pourquoi ? |
|---|---|---|
| Régénérer l’ID de session | session_regenerate_id(true); |
Empêche la fixation de session. |
| Désactiver GET pour les actions | if ($_SERVER['REQUEST_METHOD'] !== 'POST') { die(); } |
Évite les attaques via <img src="...">. |
| Utiliser des CAPTCHA | Librairie comme google/recaptcha |
Ajoute une couche de validation humaine. |
| Logger les tentatives CSRF | error_log("CSRF failed for IP: " . $_SERVER['REMOTE_ADDR']); |
Détection des attaques. |
🔧 Outils pour tester les vulnérabilités CSRF
- Extensions navigateur :
- Cookie-Editor : Pour modifier les cookies et tester les protections
SameSite. - Burp Suite : Intercepter/modifier les requêtes pour simuler une attaque.
- Cookie-Editor : Pour modifier les cookies et tester les protections
- Scripts Python :
# Exemple avec requests pour tester une vulnérabilité CSRF import requests url = "https://vulnerable-site.com/change-email" cookies = {"sessionid": "volé_ou_deviné"} # Cookie de la victime data = {"email": "hacker@evil.com"} response = requests.post(url, cookies=cookies, data=data) print(response.text) # Vérifie si l'action est exécutée - Outils automatisés :
- OWASP ZAP : Scanner de vulnérabilités CSRF.
- Nikto :
nikto -host exemple.com -C all(recherche de formulaires non protégés).
📌 Résumé des actions clés pour les développeurs PHP
| Étape | Action |
|---|---|
| 1. Identifier les zones à risque | Tous les formulaires/modifications de données (POST/GET/PUT/DELETE). |
| 2. Ajouter des tokens CSRF | Utiliser bin2hex(random_bytes(32)) ou une librairie dédiée. |
| 3. Configurer les cookies | SameSite=Strict, Secure, HttpOnly. |
| 4. Valider les en-têtes | Vérifier Referer ou Origin (avec prudence). |
| 5. Tester | Utiliser Burp Suite ou OWASP ZAP pour simuler des attaques. |
🚨 Exemple réel d’exploitation CSRF (cas historique)
Attaque sur YouTube (2008) :
- Un pirate a exploité une faille CSRF pour forcer les utilisateurs à s’abonner à des chaînes malveillantes.
- Méthode :
- Un lien piégé (
<img src="https://youtube.com/subscribe?channel=HACKER">) était partagé sur des forums. - Les victimes connectées à YouTube s’abonnaient automatiquement en visitant la page.
- Un lien piégé (
- Correctif : YouTube a ajouté des tokens CSRF et limité les actions sensibles aux requêtes POST.
📚 Ressources pour approfondir
💡 Message final :
"Le CSRF est une attaque sournoise car elle exploite un comportement normal du web : la confiance dans les cookies. La bonne nouvelle ? Avec des tokens CSRF et une configuration stricte des cookies, vous pouvez éliminer 99% des risques. Ne négligez pas cette protection – elle prend 5 minutes à implémenter et peut sauver votre application."
❓ Questions fréquentes
-
"Les tokens CSRF suffisent-ils ?" → Oui, si ils sont uniques par session/formulaire et vérifiés côté serveur. Mais combinez-les avec
SameSitepour une protection optimale. -
"Pourquoi ne pas bloquer toutes les requêtes cross-origin ?" → Certains sites légitimes ont besoin de requêtes cross-origin (ex: API publiques). Les tokens CSRF permettent une protection ciblée.
-
"Mon site est en HTTP, puis-je utiliser
SameSite?" → Non,SameSitenécessite HTTPS. Passez en HTTPS avant de l’implémenter.