3.4Sécurité : injection SQL, XSS, CSRF, hachage
L'essentiel en 30 secondes
- Ne jamais faire confiance aux entrées : toute donnée extérieure est suspecte — valider, puis traiter selon le contexte (SQL, HTML, e-mail).
- Trois failles, trois réflexes : injection SQL → requêtes préparées ; XSS → htmlspecialchars() à l'affichage ; CSRF → jeton unique vérifié + SameSite.
- Mots de passe jamais en clair : password_hash() au stockage, password_verify() à la connexion — aucun mot de passe en dur dans le code.
Mini-cas filé — Riad audite sa boutique avant mise en ligne
Riad, développeur web à Constantine, s'apprête à mettre sa boutique en ligne : commentaires affichés bruts, formulaire de commande sans jeton, mots de passe clients en clair dans la base. Il corrige tout : htmlspecialchars() sur chaque affichage, jeton CSRF unique par formulaire vérifié à la soumission, password_hash() à l'inscription et password_verify() à la connexion. L'audit passe au vert avant l'ouverture.
Dernier épisode du fil Riad — de la base aux objets jusqu'au blindage complet.
1. Injection SQL, XSS, CSRF : mécanismes et parades
La sécurité est permanente, pas une option finale, autour du principe : ne JAMAIS faire confiance aux entrées. (1) L'injection SQL (vue en 3-1) : du SQL injecté via des données concaténées — parade : requêtes préparées PDO, toujours. (2) Le XSS : du JavaScript injecté qui s'exécute dans le navigateur d'autrui quand on affiche des données non fiables sans échapper (vol de session, défacement) — parade : échapper à l'affichage avec htmlspecialchars(), toujours, pour toute donnée non fiable.
(3) Le CSRF : forcer un utilisateur connecté à exécuter une action à son insu via une requête forgée depuis un autre site — parade : jetons CSRF (valeur unique et secrète par formulaire, vérifiée à la soumission) et cookie SameSite. Riad ajoute un jeton généré par random_bytes() en champ caché de chaque formulaire sensible et le compare en session avant tout traitement.
2. Hachage, HTTPS, secrets : l'hygiène non négociable
Les mots de passe se stockent HACHÉS, jamais en clair : password_hash() à l'inscription (algorithme sûr, sel et coût intégrés), password_verify() à la connexion. Aucun mot de passe en clair ni en dur dans le code, les captures ou les messages — c'est non négociable.
Le reste de l'hygiène : HTTPS en production (chiffrer), secrets hors du code (clés et mots de passe base en variables d'environnement), erreurs détaillées jamais affichées en production (journalisées, pas divulguées), PHP et dépendances à jour, autorisation vérifiée (pas seulement l'authentification). Pour se former : l'OWASP Top 10, référence des failles web.
🔎 Exemple concret — XSS neutralisé, CSRF vérifié, mot de passe haché
Riad sécurise l'affichage d'un pseudo, un formulaire et l'inscription.
Bonjour, ' . htmlspecialchars($pseudo, ENT_QUOTES, 'UTF-8') . '';
// 2) CSRF : jeton unique en session, champ cache, verification
if (empty($_SESSION['csrf'])) {
$_SESSION['csrf'] = bin2hex(random_bytes(32));
}
// dans le formulaire :
if (!hash_equals($_SESSION['csrf'], $_POST['csrf'] ?? '')) {
http_response_code(403);
exit('Requete refusee.');
}
// 3) Hachage : jamais en clair
$hash = password_hash($motDePasseSaisi, PASSWORD_DEFAULT);
// ... stockage du $hash en base via requete preparee ...
$ok = password_verify($motDePasseSaisi, $hashStocke);
Lecture : échappement à l'affichage, jeton comparé en hash_equals, hachage puis vérification.
Leçon : trois gestes courts qui ferment trois failles graves.
Vocabulaire de la section
- Ne jamais faire confiance aux entrées
- Principe transversal : toute donnée extérieure est potentiellement malveillante — valider puis traiter selon le contexte.
Exemple simple : Riad valide l'email avant tout usage.
Ce que ce n'est pas : afficher tel quel « car ça vient de mon formulaire ».
Réflexe : suspect par défaut, validé avant usage.
- Injection SQL → préparées
- SQL injecté via concaténation ; parade : toujours des requêtes préparées PDO, jamais de concaténation.
Exemple simple : Le prepare/execute de la boutique de Riad.
Ce que ce n'est pas : une concaténation « provisoire ».
Réflexe : marqueurs + execute, sans exception.
- XSS → htmlspecialchars
- Script injecté exécuté chez d'autres visiteurs ; parade : échapper toute donnée non fiable à l'affichage.
Exemple simple : Le pseudo commenté affiché en texte inoffensif.
Ce que ce n'est pas : un echo direct d'un champ utilisateur.
Réflexe : htmlspecialchars() sur chaque affichage non fiable.
- CSRF → jetons
- Action forcée à l'insu d'un connecté ; parade : jeton unique par formulaire vérifié + cookie SameSite.
Exemple simple : Le champ caché comparé en session chez Riad.
Ce que ce n'est pas : un formulaire sensible sans aucun jeton.
Réflexe : jeton sur chaque action sensible.
- Hachage / HTTPS / secrets
- Mots de passe hachés (password_hash/verify, jamais en clair) ; HTTPS ; secrets en environnement ; erreurs masquées en prod ; à jour.
Exemple simple : L'inscription hachée et la connexion vérifiée de Riad.
Ce que ce n'est pas : un mot de passe stocké lisible en base.
Réflexe : aucun secret en clair, nulle part.
En pratique — Sécuriser une application PHP
- Contre l'injection SQL : TOUJOURS des requêtes préparées (PDO) — vérifiez qu'aucune donnée n'est concaténée dans le SQL.
- Contre le XSS : échappez toute donnée affichée avec htmlspecialchars() (commentaires, pseudos, données de base).
- Contre le CSRF : ajoutez un jeton CSRF (unique, secret) dans vos formulaires et vérifiez-le à la soumission ; utilisez SameSite sur les cookies.
- Hachez les mots de passe (password_hash/verify), utilisez HTTPS, gérez les secrets hors du code, n'affichez pas les erreurs en production, gardez PHP/dépendances à jour.
Script prêt à dire — présenter son audit sécurité (30 s)
« Avant mise en ligne : requêtes préparées partout, htmlspecialchars sur chaque affichage, jeton CSRF vérifié sur les formulaires sensibles, mots de passe hachés avec password_hash et vérifiés avec password_verify. Secrets en environnement, HTTPS en production. »
À dire en revue pré-lancement ; montrer les quatre gestes dans le code.
Modèle copier-coller — inscription et connexion sûres
prepare('INSERT INTO utilisateurs (email, mot_de_passe) VALUES (:email, :hash)');
$stmt->execute(['email' => $email, 'hash' => $hash]);
// Connexion : lire en prepare, verifier le hash
$stmt = $pdo->prepare('SELECT id, mot_de_passe FROM utilisateurs WHERE email = :email');
$stmt->execute(['email' => $emailSaisi]);
$utilisateur = $stmt->fetch();
if ($utilisateur && password_verify($motDePasseSaisi, $utilisateur['mot_de_passe'])) {
session_regenerate_id(true);
$_SESSION['utilisateur_id'] = $utilisateur['id'];
}
// Affichage : toujours echapper
$pseudoAffiche = htmlspecialchars($pseudo, ENT_QUOTES, 'UTF-8');
Indépendant
Préparées + htmlspecialchars + hachage : le trio suffit à lancer sans faute grave.
PME à Constantine
Riad ajoute les jetons CSRF aux bons de commande avant l'ouverture.
Équipe produit
Imposer la check-list des quatre gestes en revue de code systématique.
Débutant
Cette semaine : un affichage échappé et une inscription hachée.
✓ Bonnes pratiques
- Penser sécurité dès la conception, à chaque couche (entrée, SQL, HTML, action, stockage).
- Utiliser les outils éprouvés (préparées, htmlspecialchars, password_hash, jetons) plutôt que du maison.
- Se former en continu (OWASP Top 10) : les attaques évoluent.
✗ Pièges fréquents
- Afficher une donnée de « sa propre base » sans échapper (elle vient d'utilisateurs).
- Oublier le jeton CSRF sur une action sensible (commande, suppression).
- Afficher les erreurs détaillées en production (informations divulguées).
Points clés à retenir
- La SÉCURITÉ est PERMANENTE, autour du principe : NE JAMAIS FAIRE CONFIANCE aux entrées (tout l'extérieur est suspect).
- INJECTION SQL → PRÉPARÉES (jamais de concaténation). XSS → ÉCHAPPER avec htmlspecialchars(). CSRF → JETONS + SameSite.
- Mots de passe HACHÉS (password_hash/verify, JAMAIS en clair ni en dur). HTTPS, SECRETS hors code, erreurs masquées en prod, À JOUR.
- Outils ÉPROUVÉS + formation (OWASP Top 10). Un site non sécurisé met ses utilisateurs en danger — responsabilité professionnelle.
Défi 48h — appliquez demain
Ajoutez 1 jeton CSRF vérifié à un formulaire, échappez 3 affichages et convertissez 1 stockage en password_hash.
- Je cite les quatre parades sans hésiter.
- Aucune donnée n'est concaténée ni affichée brute.
- Aucun mot de passe n'existe en clair dans mon code.
Questions fréquentes
XSS et CSRF : concrètement, comment s'en protéger ?
Le XSS : un attaquant glisse du JavaScript dans un champ (commentaire, pseudo) affiché sans échapper — il s'exécute chez les visiteurs (vol de session, redirection). Parade : htmlspecialchars() sur toute donnée non fiable avant affichage (le script devient du texte inoffensif). Le CSRF : une requête forgée depuis un autre site fait agir un connecté à son insu (changer un email, commander). Parade : jeton unique et secret par formulaire, vérifié à la soumission (+ SameSite sur les cookies). Deux mécanismes différents, deux parades systématiques.
Par où commencer en sécurité, et quel état d'esprit adopter ?
L'état d'esprit : « ne jamais faire confiance aux entrées » — tout l'extérieur est suspect (même « votre » interface, contournable en requêtes directes). Les fondamentaux à fort impact : (1) injection SQL → préparées, (2) XSS → échapper, (3) CSRF → jetons, (4) mots de passe → hachage, (5) HTTPS, (6) secrets hors code, (7) erreurs masquées en prod, (8) autorisation (droits, pas juste connexion), (9) uploads verrouillés. Puis OWASP Top 10 pour structurer la suite.
Un visiteur poste un pseudo avec du JavaScript. Réflexe correct ?