La sécurité des formulaires PHP

security-php

Il est un domaine que l’on ne doit pas oublier lorsque l’on réalise des formulaires en PHP, c’est la sécurité. En effet, les formulaires sont très souvent liés à une adresse mail ou à une base de données ; il est donc judicieux de protéger ces éléments-clé d’un système d’information.

Loin d’être un guide absolu dans le domaine, cet article regroupera quelques bonnes pratiques pour sécuriser un formulaire que j’ai glanées au fil du web.

Choisir la méthode du formulaire

Le premier choix qui s’impose lors de la réalisation d’un formulaire, c’est la méthode de transmission des données d’une page à une autre. Il en existe deux : GET, et POST. Alors que la méthode GET transmet les variables récupérées dans un formulaire par l’URL, la méthode POST les transmet de façon masquée.

Il vous faudra donc choisir, au cas par cas, si les variables récupérées peuvent être affichées au visiteur dans l’URL, ou non. Notez que ce n’est pas parce que les variables sont transmises en POST qu’elles sont cryptées ; il reste possible de les afficher avec des petits outils.

Les failles XSS (Cross Site Scripting)

Pour profiter d’une faille XSS, l’attaquant va tenter d’insérer un code (JavaScript, le plus souvent) dans une case d’un formulaire non protégé, ceci afin de modifier le fonctionnement de la page.

Les injections SQL

Pour exécuter une injection SQL, l’attaquant va tenter d’insérer un code dans une case d’un formulaire non protégé qui est reliée à une base de données. Dans le pire des cas, ce code peut servir à supprimer une table de la base de données, ou toute la base ! Mais grâce à une injection SQL, on peut aussi se connecter sous n’importe quel nom d’utilisateur, et ainsi passer outre le système login/mdp d’un site web.

Insertion dans une base de données

Pour être sûr que des attaquants n’exécutent pas des injections SQL sur notre base de données, nous devons protéger l’insertion d’une saisie utilisateur grâce aux fonctions :

  • mysql_real_escape_string() : échappe les caractères spéciaux (notamment ‘  » et NULL) par un antislashs ; étant donné que c’est une fonction de MySQL, vous aurez besoin de vous connecter à votre base avec un mysql_connect() avant de pouvoir l’utiliser
  • addslashes() : réalise la même chose que mysql_real_escape_string(), mais n’est à utiliser que si vous utilisez un type de base de donnée qui ne propose pas ce genre de fonction

Affichage depuis une base de données (ou saisie utilisateur)

Trois fonctions s’offrent à nous pour éviter les attaques XSS. Avant de les utiliser, vous devrez vous demander si vous souhaitez que les utilisateurs puissent stocker du code source dans votre base, ou si vous considérez que rien de ce qui n’entre dans votre base n’a besoin d’être du code. Dans le cas d’un forum par exemple, un utilisateur peut légitimement poster un message avec du code sain (utilisez alors l’une des deux premières fonctions).

  • htmlspecialchars() : encode les caractères < > ‘  » & pour qu’ils soient affichés mais pas exécutés
  • htmlentities() : la même chose que htmlspecialchars(), à la différence qu’elle encode beaucoup plus de caractères ; par exemple, le symbole €, les lettres accentuées é à ù…
  • strip_tags() : supprime carrément toutes les balises contenant < et > qui ne seront pas affichées

Enfin, si vous souhaitez récupérer des données qui ont été insérées en étant protégées par htmlspecialchars(), htmlentities() ou addslashes(), il vous faudra utiliser respectivement htmlspecialchars_decode(), html_entity_decode() et stripslashes().

Exemple concret

Pour finir, voilà un exemple d’un formulaire, avec dessous, la partie « sécurisation » (remarque : ce code sera prochainement obsolète car PHP pousse à l’utilisation de la « PDO » ; voir mon commentaire plus bas pour un exemple d’utilisation) :

<form id="mon_formulaire" method="POST" action="formulaire.php">
	<input name=premier_champ type=text placeholder="Premier champ" required>
	<input name=second_champ type=number placeholder="Second champ" required>
<button type=submit name=envoi>Envoi</button>

<?php
if (isset($_POST['envoi']))
{
	// Connexion à la base de données
	mysql_connect($dbhost, $dbuser, $dbpass);
	mysql_select_db($dbname);
	
	// Sécurisation des données reçues
	$premier_champ = mysql_real_escape_string($premier_champ);
	$second_champ = mysql_real_escape_string($second_champ);
	
	// Création et envoi de la requête
	$sql = 'INSERT INTO ma_table VALUES("'.$premier_champ.'","'.$second_champ.'")'; 
	mysql_query ($sql) or die ('Erreur SQL !'.$sql.'<br />'.mysql_error());  
	
	//Clotûre de la connexion à la base de données.
	mysql_close (); 

	// Affichage des résultats à l'utilisateur
	echo 'Vous avez inséré '.htmlspecialchars($premier_champ).'.';
}
?>
</form>

Voilà donc quelques méthodes simples pour éviter que votre formulaire soit une passoire :) n’hésitez pas à m’indiquer dans les commentaires si vous avez d’autres astuces pour la sécurisation simple des insertions et lectures de base de données.

XKCD - Exploits of a mom

Pour aller plus loin
StackOverflow – How to prevent code injection attacks in PHP (EN)
Webmaster Hub – Sécurité et formulaires PHP
Apprendre PHP – Traitement des formulaires
Chez Neg – Sécurité d’un formulaire PHP
Bastien Rossi – Les injections SQL
Le blog de maniT4c – Protection de formulaire contre les robots

2 comments

  1. Bonjour,
    Très intéressant mais il parle d’abandonner MySQL_connect & MySQL_real_escape_string pour PDO par exemple.
    Comment faire donc ?
    (source doc php Avertissement Cette extension est obsolète depuis PHP 5.5.0, et sera supprimée dans le futur)

    1. Je n’ai pas encore pris l’habitude d’utiliser la PDO. À ce que j’ai lu, en PDO, il faut passer par des requêtes préparées (« prepared statements ») qui éliminent le besoin de sécuriser les variables insérées dans la requête. Petit exemple :

      $pdo = new PDO(« mysql:host=localhost; dbname=mydb; charset=utf8″, « user », « password »);
      $query = $pdo->prepare(« INSERT INTO test (id, nom) VALUES (:id, :nom) »);
      $query->bindParam(‘id’, $_GET['id'], PDO::PARAM_INT);
      $query->bindParam(‘nom’, $_GET['nom'], PDO::PARAM_STR);
      $result = $query->execute():

      « Si votre application utilise exclusivement les requêtes préparées, vous pouvez être sûr qu’aucune injection SQL n’est possible. » (vu sur la doc de PHP)

      Quelques liens :
      http://fr.openclassrooms.com/forum/sujet/pdo-gt-mysqlrealescapestring-19722
      http://stackoverflow.com/a/14012675
      http://www.php.net/manual/fr/pdo.prepared-statements.php
      http://fr.php.net/manual/fr/class.pdo.php

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée.

Vous pouvez utiliser ces balises et attributs HTML : <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>