Sécuriser un formulaire PHP : validation, échappement et jeton CSRF

Apprenez à sécuriser un formulaire PHP contre les données invalides, les injections et les requêtes CSRF grâce à une méthode claire de validation, d’échappement et de contrôle du jeton.

Un formulaire PHP ne devient pas sûr simplement parce que ses champs sont obligatoires. Les données envoyées par le navigateur peuvent être modifiées, complétées ou remplacées avant d’atteindre le serveur. La protection doit donc être appliquée côté serveur, même si une validation JavaScript est déjà présente dans la page.

Une méthode fiable repose sur trois mécanismes complémentaires : valider les données reçues, les traiter correctement selon leur usage et vérifier qu’une requête provient bien de votre formulaire grâce à un jeton CSRF. Ces protections ne répondent pas au même problème et ne doivent pas être confondues.

Ce que votre formulaire doit vérifier avant tout traitement

La première question n’est pas « le champ est-il rempli ? », mais plutôt : quelle valeur est réellement attendue ici ? Un nom, une adresse e-mail, un identifiant numérique et un message libre ne se contrôlent pas de la même manière.

Le navigateur peut afficher une contrainte avec des attributs comme required, type=”email” ou minlength. Ces indications améliorent l’expérience utilisateur, mais elles ne constituent pas une mesure de sécurité. Un utilisateur peut envoyer directement une requête qui ignore totalement le code HTML de la page.

Côté PHP, commencez par vérifier la méthode HTTP attendue. Si le formulaire est conçu pour créer ou modifier une donnée, il doit généralement accepter uniquement une requête POST. Une requête GET inattendue ne devrait pas déclencher la même opération.

Validation et nettoyage ne désignent pas la même opération

La validation consiste à décider si une donnée respecte les règles prévues : format d’e-mail correct, longueur acceptable, valeur appartenant à une liste autorisée ou identifiant numérique valide.

Le nettoyage tente de modifier une donnée pour la rendre plus présentable ou compatible avec un traitement. Il ne doit pas remplacer la validation. Supprimer certains caractères d’une valeur ne garantit pas que cette valeur est correcte et peut même masquer une tentative ou altérer une information légitime.

Pour un champ de statut, par exemple, il est préférable de comparer la valeur reçue à une liste blanche comme « brouillon », « publié » ou « archivé ». Accepter n’importe quelle chaîne puis essayer de la nettoyer crée une règle moins prévisible.

Construire une validation serveur adaptée à chaque champ

Software developer analyzing code on a tablet in a modern office workspace.

Une validation utile combine le type attendu, les limites acceptables et le contexte fonctionnel. Elle doit aussi produire des erreurs compréhensibles sans révéler d’informations internes sur le serveur.

Champ Contrôles recommandés Point de vigilance
Adresse e-mail Présence, format, longueur maximale Une adresse valide ne prouve pas que la boîte existe
Identifiant Valeur entière, plage autorisée Vérifier aussi que l’objet appartient à l’utilisateur connecté
Choix prédéfini Comparaison avec une liste blanche Ne jamais accepter une valeur arbitraire
Message libre Présence, longueur minimale et maximale Échapper le contenu lorsqu’il est réaffiché

La longueur maximale est souvent oubliée. Elle protège à la fois l’interface, la base de données et les traitements qui suivent. Une limite doit rester cohérente avec le schéma de stockage et avec le besoin métier : un commentaire n’a pas les mêmes contraintes qu’un code postal.

Après validation, utilisez des valeurs typées autant que possible. Pour un identifiant, convertissez et contrôlez une valeur entière plutôt que de la concaténer dans une requête. Pour une date, vérifiez le format attendu puis confirmez que la date est cohérente avec la règle métier.

Ne faites pas confiance aux noms de champs ni aux valeurs cachées

Un champ masqué dans le formulaire n’est pas secret. Il peut être modifié comme n’importe quelle autre valeur envoyée par le navigateur. Il ne doit donc jamais être utilisé seul pour déterminer un prix, un rôle, une autorisation ou l’identité d’un utilisateur.

Les informations sensibles doivent être recalculées ou vérifiées côté serveur. Si un formulaire reçoit l’identifiant d’un article à modifier, PHP doit contrôler que cet article existe et que l’utilisateur dispose réellement du droit de le modifier.

Échapper les données au moment où elles sont affichées

Une donnée peut être acceptable pour la base de données mais dangereuse lorsqu’elle est injectée dans une page HTML. C’est le principe fondamental de l’échappement contextuel : le traitement dépend de l’endroit où la valeur sera utilisée.

Pour afficher une donnée fournie par un utilisateur dans du HTML, échappez les caractères spéciaux afin qu’ils soient interprétés comme du texte et non comme des balises ou du code. En PHP, la fonction habituellement utilisée dans ce contexte est htmlspecialchars, avec une configuration adaptée à l’encodage de la page.

Cette protection doit être appliquée au moment de la sortie, pas uniquement lors de l’enregistrement. Une même donnée peut être affichée dans le contenu HTML, dans un attribut, dans une URL ou dans du JavaScript ; chacun de ces contextes possède ses propres règles. Une fonction prévue pour du HTML ne doit pas être considérée comme une protection universelle pour tous les emplacements.

Évitez également de réinjecter directement dans le formulaire une valeur reçue sans contrôle. Lorsqu’un formulaire est réaffiché après une erreur, les champs préremplis doivent être échappés comme toute autre donnée dynamique.

Protéger les requêtes SQL sans bricolage

La validation ne suffit pas à protéger une requête SQL. Même une valeur qui semble correcte peut être utilisée de manière dangereuse si elle est concaténée directement dans une chaîne SQL.

Utilisez des requêtes préparées avec PDO ou une bibliothèque équivalente. Les paramètres sont alors transmis séparément de la structure de la requête, ce qui évite de traiter une donnée utilisateur comme une partie de l’instruction SQL.

Le principe est simple : la requête définit ses paramètres, puis le code associe les valeurs reçues à ces paramètres. Ne construisez pas une requête en assemblant des fragments provenant directement de POST, de GET ou de cookies.

Les requêtes préparées ne remplacent toutefois pas les contrôles métier. Elles empêchent une injection SQL, mais elles ne vérifient pas qu’un utilisateur a le droit d’accéder à l’enregistrement demandé. L’authentification, l’autorisation et la protection SQL répondent à des problèmes différents.

Ajouter un jeton CSRF aux formulaires qui modifient des données

Close-up of a finger entering a passcode on a smartphone security screen.

Une attaque CSRF pousse un navigateur déjà authentifié à envoyer une requête vers votre site sans que la victime ait réellement souhaité cette action. Le navigateur peut automatiquement joindre le cookie de session, ce qui rend l’action crédible pour le serveur.

Le jeton CSRF, ou Cross-Site Request Forgery token, permet de vérifier que la requête provient d’une page générée par votre application. Le serveur crée une valeur aléatoire imprévisible, la conserve dans la session, puis l’insère dans le formulaire. Au moment de la réception, il compare la valeur envoyée avec celle de la session.

  1. Créer un jeton aléatoire lors de l’affichage du formulaire, si la session n’en possède pas déjà un.
  2. Insérer ce jeton dans un champ caché du formulaire.
  3. Récupérer le jeton envoyé avec la requête POST.
  4. Comparer les deux valeurs avec une comparaison résistante aux attaques par analyse du temps.
  5. Refuser la requête si le jeton est absent, incorrect ou associé à une session invalide.

Le jeton doit être généré avec un générateur aléatoire cryptographiquement sûr. Il ne doit pas être prévisible à partir de l’identifiant de l’utilisateur, de la date ou d’une valeur visible dans la page.

Cette protection concerne principalement les actions qui créent, modifient ou suppriment des données. Pour les formulaires publics, comme un contact ou un commentaire, elle reste utile, mais elle doit souvent être complétée par une limitation de fréquence, une détection des abus et une protection contre le spam.

Le cookie SameSite complète mais ne remplace pas le jeton

Les attributs de cookie comme SameSite peuvent réduire l’envoi de cookies dans certains scénarios intersites. Ils constituent une bonne mesure complémentaire, au même titre que les attributs Secure et HttpOnly lorsque le contexte le permet.

Ils ne doivent cependant pas être considérés comme l’unique défense. Les règles de compatibilité, les sous-domaines, les intégrations et certains flux légitimes peuvent modifier le comportement attendu. Le contrôle explicite du jeton reste une approche claire pour les formulaires sensibles.

Gérer les erreurs sans divulguer d’informations

Un formulaire sécurisé doit être utilisable. Après une erreur, indiquez le champ à corriger et expliquez la règle attendue : adresse incomplète, valeur non autorisée ou message trop long. Ne retournez pas une trace PHP, un chemin de fichier, une requête SQL ou le détail d’une exception.

Conservez éventuellement les valeurs non sensibles pour éviter à l’utilisateur de tout ressaisir. En revanche, ne réaffichez pas automatiquement un mot de passe, un jeton ou une donnée confidentielle. Après une opération réussie, la redirection vers une page de résultat permet aussi d’éviter une nouvelle soumission lors d’un rafraîchissement.

Les erreurs techniques doivent être enregistrées dans un dispositif réservé à l’administration, avec des droits d’accès appropriés. Les messages présentés au public doivent rester génériques, tandis que les journaux internes peuvent contenir le contexte nécessaire au diagnostic, sans stocker de secrets inutiles.

La vérification finale avant de mettre le formulaire en ligne

Avant publication, testez le formulaire comme un utilisateur normal, mais aussi avec des données inattendues. Le but n’est pas de chercher une combinaison spectaculaire : il s’agit de vérifier que chaque règle importante est réellement appliquée côté serveur.

  • La requête est-elle refusée si elle n’utilise pas la méthode prévue ?
  • Les champs obligatoires et leurs longueurs sont-ils contrôlés côté serveur ?
  • Les valeurs à choix limité sont-elles comparées à une liste autorisée ?
  • Les requêtes vers la base utilisent-elles des paramètres séparés ?
  • Les données réaffichées dans le HTML sont-elles échappées ?
  • Le jeton CSRF est-il vérifié avant toute modification ?
  • Les droits de l’utilisateur sont-ils contrôlés sur l’objet demandé ?
  • Les messages d’erreur évitent-ils les détails techniques sensibles ?
  • Les soumissions répétées et les abus prévisibles sont-ils limités ?

La sécurité d’un formulaire ne repose donc pas sur une fonction unique. La validation garantit que la donnée correspond au besoin, l’échappement protège son affichage, les requêtes préparées isolent les paramètres SQL et le jeton CSRF confirme l’origine attendue de l’action. En les appliquant ensemble, puis en testant les autorisations et les erreurs, vous obtenez une base solide pour les formulaires PHP courants.

À propos de l’auteur

Maya Connect

Maya Connect est la voix éditoriale de Clic Connect. Curieuse, méthodique et attentive aux usages concrets, elle transforme les sujets numériques complexes en parcours compréhensibles et applicables. Elle relie chaque notion à un objectif précis, explicite les prérequis et distingue systématiquement les faits établis, les bonnes pratiques et les pistes à expérimenter.