3.4Sécurité : injection SQL, XSS, CSRF, hachage de mots de passe
La sécurité est une préoccupation ESSENTIELLE et permanente du développement web — pas une option à ajouter à la fin. Les applications web sont exposées (accessibles au monde entier), manipulent des données sensibles, et sont des cibles constantes d'attaques. Un développeur PHP DOIT connaître les failles courantes et leurs protections — c'est une responsabilité professionnelle et éthique (vous protégez les données et comptes de vraies personnes). Cette section synthétise les principales failles web (déjà croisées) et leurs protections, autour d'un principe fondamental et transversal : ne JAMAIS faire confiance aux entrées (données utilisateur, requêtes, tout ce qui vient de l'extérieur est potentiellement malveillant). Passons en revue les failles majeures. (1) L'injection SQL (vue en 3-1) : injecter du SQL malveillant via des données concaténées dans une requête. Protection : les requêtes préparées (PDO) — TOUJOURS, ne jamais concaténer de données dans le SQL.
(2) Le XSS (Cross-Site Scripting) : injecter du code JavaScript malveillant qui s'exécutera dans le NAVIGATEUR d'autres utilisateurs. Cela arrive quand on AFFICHE des données non fiables (venant de l'utilisateur ou de la base) sans les échapper — un attaquant place du script dans un champ (un commentaire, un pseudo), et ce script s'exécute chez les visiteurs (vol de session, redirection, défacement). Protection : ÉCHAPPER les données à l'affichage avec htmlspecialchars() (qui transforme les caractères spéciaux HTML en entités inoffensives) — TOUJOURS, avant d'afficher toute donnée non fiable. (3) Le CSRF (Cross-Site Request Forgery) : forcer un utilisateur connecté à exécuter une action à son insu (via une requête forgée depuis un autre site). Protection : les jetons CSRF (un jeton unique et secret dans chaque formulaire, vérifié à la soumission — garantissant que la requête vient bien de votre site) et l'attribut de cookie SameSite. (4) Le hachage des mots de passe (vu en 2-2) : ne JAMAIS stocker un mot de passe en clair — utiliser password_hash() (algorithme sûr, avec sel, coût ajustable) et password_verify(). Autres points de sécurité importants : utiliser HTTPS (chiffrer les communications), gérer les secrets (clés, mots de passe de base) hors du code/dépôt (variables d'environnement), configurer PHP pour NE PAS afficher les erreurs détaillées en PRODUCTION (elles peuvent divulguer des informations sensibles — les journaliser plutôt que les afficher), maintenir PHP et les dépendances À JOUR (les failles connues sont corrigées par les mises à jour), limiter les accès (autorisation), valider TOUTES les entrées. La sécurité est un domaine vaste et évolutif ; une référence utile est l'OWASP Top 10 (la liste des principales failles web) pour se former de façon structurée. L'état d'esprit à adopter : penser sécurité DÈS la conception, ne jamais faire confiance aux entrées, s'appuyer sur des outils et pratiques éprouvés (requêtes préparées, password_hash, htmlspecialchars, jetons CSRF, frameworks), et rester vigilant. Maîtriser ces bases de sécurité (les failles courantes et leurs protections) n'est pas optionnel : c'est une compétence FONDAMENTALE du développeur web, aux enjeux réels (les fuites et compromissions font l'actualité et ont des conséquences graves). Un site non sécurisé est un danger pour ses utilisateurs.
Vocabulaire de la section
- Ne jamais faire confiance aux entrées
- Principe fondamental et transversal de la sécurité web : toute donnée venant de l'extérieur (utilisateur, requête) est potentiellement malveillante — à valider et traiter avec prudence.
- Injection SQL → requêtes préparées
- Injecter du SQL via des données concaténées ; protection : TOUJOURS des requêtes préparées (PDO), jamais de concaténation dans le SQL.
- XSS → htmlspecialchars
- Injecter du JavaScript qui s'exécute chez d'autres utilisateurs (affichage de données non échappées) ; protection : ÉCHAPPER à l'affichage avec htmlspecialchars().
- CSRF → jetons
- Forcer un utilisateur connecté à exécuter une action à son insu (requête forgée) ; protection : jetons CSRF (unique par formulaire, vérifié) et cookie SameSite.
- Hachage / HTTPS / secrets
- Mots de passe HACHÉS (password_hash/verify, jamais en clair) ; HTTPS (chiffrer) ; secrets hors du code (variables d'environnement) ; erreurs non affichées en production ; dépendances à jour.
Quelles failles web fondamentales tout développeur PHP doit-il prévenir ?
En pratique — Sécuriser une application PHP
- Contre l'injection SQL : utilisez 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.
Points clés à retenir
- La SÉCURITÉ est une préoccupation PERMANENTE (pas une option finale), autour du principe : NE JAMAIS FAIRE CONFIANCE aux entrées (tout ce qui vient de l'extérieur est potentiellement malveillant).
- INJECTION SQL → requêtes PRÉPARÉES (PDO, jamais de concaténation). XSS (script injecté chez d'autres) → ÉCHAPPER à l'affichage avec htmlspecialchars(). CSRF (action forcée à l'insu) → JETONS CSRF + cookie SameSite.
- Mots de passe HACHÉS (password_hash/verify, JAMAIS en clair). HTTPS (chiffrer), SECRETS hors du code (variables d'environnement), erreurs NON affichées en production, PHP/dépendances À JOUR.
- S'appuyer sur des outils ÉPROUVÉS (requêtes préparées, password_hash, htmlspecialchars, jetons CSRF, frameworks) ; se former (OWASP Top 10). Compétence FONDAMENTALE aux enjeux réels — un site non sécurisé met en danger ses utilisateurs.
Questions fréquentes
Les failles XSS et CSRF : qu'est-ce que c'est concrètement, et comment s'en protéger ?
Le XSS et le CSRF sont deux failles web majeures (parmi les plus courantes), différentes dans leur mécanisme mais toutes deux graves — les comprendre concrètement et connaître leurs protections est essentiel. Le XSS (Cross-Site Scripting) : (1) Ce que c'est : une faille où un attaquant injecte du code JAVASCRIPT malveillant qui s'exécutera dans le NAVIGATEUR d'AUTRES utilisateurs de votre site. (2) Comment ça arrive : quand votre site AFFICHE des données NON FIABLES (venant d'un utilisateur ou de la base) SANS les échapper. Exemple : un site de commentaires où les commentaires sont affichés tels quels. Un attaquant poste un « commentaire » contenant du code JavaScript (un <script> malveillant). Quand d'autres utilisateurs consultent la page, ce script S'EXÉCUTE dans LEUR navigateur. (3) Les conséquences : le script malveillant peut VOLER les cookies/sessions des visiteurs (usurper leur identité), REDIRIGER vers un site malveillant, MODIFIER la page (défacement), enregistrer les frappes clavier, etc. Le XSS compromet les VISITEURS de votre site (pas directement votre serveur). (4) La protection : ÉCHAPPER à l'affichage. La règle : ÉCHAPPEZ toute donnée non fiable AVANT de l'afficher, avec htmlspecialchars(). Cette fonction transforme les caractères spéciaux HTML (comme <, >, &, guillemets) en ENTITÉS HTML inoffensives — ainsi, un <script> injecté s'affiche comme du TEXTE (visible mais inoffensif) au lieu d'être interprété comme du code. TOUJOURS échapper les données affichées (commentaires, pseudos, tout contenu venant de l'utilisateur ou de la base). C'est LA protection contre le XSS. Les moteurs de template (Blade, Twig — niveau 4) échappent AUTOMATIQUEMENT par défaut (un avantage de sécurité). Point clé : l'échappement dépend du CONTEXTE (HTML, attribut, JavaScript, URL) — htmlspecialchars pour le HTML ; d'autres contextes demandent d'autres échappements. Mais pour l'affichage HTML courant, htmlspecialchars est la base. Le CSRF (Cross-Site Request Forgery) : (1) Ce que c'est : une faille où un attaquant FORCE un utilisateur CONNECTÉ à exécuter une action à son INSU, en exploitant le fait que son navigateur envoie automatiquement ses cookies (donc sa session) avec les requêtes. (2) Comment ça arrive : imaginez qu'un utilisateur est connecté à votre site (banque, réseau social). Un attaquant l'attire sur un AUTRE site (ou lui envoie un lien/email) contenant une requête cachée vers VOTRE site (par exemple, un formulaire ou une image qui déclenche une action : « transférer de l'argent », « changer l'email », « supprimer le compte »). Comme le navigateur de la victime envoie automatiquement ses cookies de session de votre site, la requête est traitée comme LÉGITIME (l'utilisateur est connecté) — l'action est exécutée à son insu. (3) Les conséquences : des actions non voulues exécutées au nom de l'utilisateur (virement, changement de données, suppression). (4) La protection : les JETONS CSRF. Le principe : ajouter dans chaque formulaire (ou action sensible) un JETON CSRF — une valeur UNIQUE, SECRÈTE et IMPRÉVISIBLE, générée par votre site et stockée en session. À la soumission, votre site VÉRIFIE que le jeton reçu correspond à celui attendu. Comme un attaquant (sur un autre site) NE PEUT PAS connaître ce jeton secret, il ne peut pas forger une requête valide — la requête forgée n'a pas le bon jeton, elle est REJETÉE. Le jeton garantit que la requête vient bien de VOTRE site (d'un formulaire légitime). Protection complémentaire : l'attribut de cookie SameSite (qui limite l'envoi des cookies aux requêtes venant de votre propre site) réduit aussi le risque CSRF. Les frameworks (Laravel, Symfony) gèrent les jetons CSRF AUTOMATIQUEMENT (un avantage majeur — ils protègent par défaut). La différence entre les deux : (1) XSS : injecter du CODE (JavaScript) qui s'exécute chez les VISITEURS — protection : échapper à l'affichage (htmlspecialchars). (2) CSRF : forcer une ACTION à l'insu d'un utilisateur connecté (requête forgée) — protection : jetons CSRF (+ SameSite). Le XSS compromet les visiteurs via du code injecté ; le CSRF exploite la session pour forcer des actions. Les protections résumées : (1) Contre le XSS : ÉCHAPPER toute donnée affichée (htmlspecialchars), ne jamais afficher de données non fiables brutes. Les moteurs de template échappent par défaut. (2) Contre le CSRF : JETONS CSRF dans les formulaires (vérifiés à la soumission), cookie SameSite. Les frameworks le gèrent automatiquement. En résumé : le XSS (injection de JavaScript qui s'exécute chez d'autres visiteurs, via des données affichées non échappées) se protège en ÉCHAPPANT à l'affichage (htmlspecialchars) ; le CSRF (forcer une action à l'insu d'un utilisateur connecté, via une requête forgée exploitant sa session) se protège avec des JETONS CSRF (+ SameSite). Ce sont deux failles majeures et courantes, aux protections bien établies. Appliquez-les systématiquement (échapper à l'affichage, jetons CSRF), et sachez que les frameworks les gèrent largement automatiquement (un argument fort pour les utiliser). Avec l'injection SQL (requêtes préparées) et le hachage des mots de passe (password_hash), le XSS et le CSRF font partie des failles fondamentales que tout développeur web DOIT connaître et prévenir — c'est la base de la sécurité web, et une responsabilité envers vos utilisateurs.
La sécurité web semble vaste et intimidante : par où commencer et quel état d'esprit adopter ?
La sécurité web EST vaste, mais on peut l'aborder par les fondamentaux à fort impact, et surtout en adoptant le bon ÉTAT D'ESPRIT — qui compte plus que de connaître toutes les failles (nous l'avons vu pour Node, les principes sont universels et s'appliquent identiquement à PHP). L'état d'esprit fondamental : « ne jamais faire confiance aux entrées ». C'est LE principe qui sous-tend toute la sécurité web. Tout ce qui vient de l'EXTÉRIEUR (données de formulaires, paramètres d'URL, en-têtes, corps de requêtes, fichiers uploadés, et même les requêtes venant de « votre » interface — car un attaquant peut envoyer des requêtes directement) est POTENTIELLEMENT MALVEILLANT. Traitez toute entrée comme suspecte : validez-la, assainissez-la, manipulez-la avec prudence (requêtes préparées, échappement selon le contexte). Cette posture défensive — supposer le pire des entrées — vous fait éviter une grande partie des failles, même celles que vous ne connaissez pas nommément. Par où COMMENCER (les fondamentaux à fort impact) : (1) Injection SQL → requêtes préparées (PDO, jamais de concaténation). Fondamental et fréquent. (2) XSS → échapper à l'affichage (htmlspecialchars). Ne jamais afficher de données non fiables brutes. (3) CSRF → jetons CSRF dans les formulaires (+ SameSite). (4) Mots de passe → hachage (password_hash/password_verify, jamais en clair). (5) HTTPS : chiffrer les communications (indispensable en production). (6) Secrets protégés : clés, mots de passe de base hors du code et du dépôt Git (variables d'environnement). (7) Erreurs en production : NE PAS afficher les erreurs détaillées aux utilisateurs (elles divulguent des informations exploitables) — les journaliser plutôt. (8) Autorisation : vérifier les DROITS d'accès (pas juste l'authentification). (9) Uploads sécurisés (section 2-3). (10) Dépendances à jour (les failles connues sont corrigées par les mises à jour ; Composer aide à gérer les versions). Avec ces fondamentaux, vous couvrez déjà l'essentiel des risques courants. Comment PROGRESSER (sans se noyer) : (1) Commencez par les fondamentaux ci-dessus — meilleur rapport impact/effort. Ne cherchez pas à tout maîtriser d'un coup. (2) Apprenez les failles COURANTES progressivement : l'OWASP TOP 10 (la liste de référence des principales failles web) est un excellent guide d'apprentissage structuré — étudiez-le pour connaître les failles majeures et leurs protections. (3) Utilisez des OUTILS et pratiques ÉPROUVÉS : requêtes préparées, password_hash, htmlspecialchars, jetons CSRF, et surtout les FRAMEWORKS (Laravel, Symfony) qui intègrent de nombreuses protections PAR DÉFAUT (échappement automatique dans les templates, jetons CSRF automatiques, systèmes d'auth robustes, requêtes préparées via l'ORM). Utiliser un framework est en soi un gain de sécurité majeur — il évite beaucoup d'erreurs. Ne réinventez pas la sécurité. (4) Restez informé et à jour : la sécurité évolue (nouvelles failles, correctifs) ; gardez PHP et vos dépendances à jour, suivez les bonnes pratiques. (5) Faites relire / auditer sur les projets sérieux. L'état d'esprit global : (1) la sécurité est une PRÉOCCUPATION PERMANENTE, intégrée dès la CONCEPTION (« comment cette entrée pourrait-elle être abusée ? », « qui a le droit d'accéder à ça ? »), pas ajoutée à la fin. (2) Posture DÉFENSIVE (ne pas faire confiance aux entrées, supposer le pire). (3) Acceptez de ne pas tout savoir — commencez par les fondamentaux, apprenez progressivement (OWASP), utilisez des outils éprouvés (frameworks). (4) Prenez la RESPONSABILITÉ au sérieux : vous manipulez les données et comptes de vraies personnes ; une faille peut leur nuire réellement (fuite de données, comptes compromis). La sécurité est une obligation éthique et professionnelle. En résumé : ne soyez pas paralysé par l'ampleur de la sécurité — commencez par les fondamentaux à fort impact (requêtes préparées contre l'injection SQL, htmlspecialchars contre le XSS, jetons CSRF, password_hash, HTTPS, secrets protégés, erreurs non affichées, autorisation, dépendances à jour), adoptez l'état d'esprit défensif (« ne jamais faire confiance aux entrées »), appuyez-vous sur des outils éprouvés (et les frameworks qui protègent par défaut), et apprenez progressivement les failles courantes (OWASP Top 10). L'état d'esprit (méfiance envers les entrées, sécurité dès la conception, responsabilité) compte plus que de connaître toutes les failles par cœur. Un développeur qui INTÈGRE ces réflexes de base et cette posture évite déjà l'immense majorité des problèmes. La sécurité est un pilier du métier de développeur web — prenez-la au sérieux dès le début, progressivement mais résolument. Ce n'est pas optionnel : un site non sécurisé met en danger ses utilisateurs, et la sécurité est une compétence fondamentale et une responsabilité de tout développeur PHP.
Autres ressources
password_hash() / password_verify().