Si une valeur contient elle-même une apostrophe, comme « L’ours » dans l’exemple ci-dessus, MySQL est perdu et produit un message d’erreur.. Quand l’option magic_quotes_gpc vaut On, un t
Trang 1< i n p u t t y p e = ’ h i d d e n ’ name = ’ e n v o y e r ’ v a l u e = ’ 1 ’ / >
< t a b l e >
< t r >< t h > D e s t i n a t a i r e : < / t h >
< t d >< i n p u t t y p e = ’ t e x t ’ s i z e = ’ 4 0 ’ name = ’ d e s t i n a t a i r e ’ / >< / t d >
< / t r >
< t r >< t h > S u j e t : < / t h >
< t d >< i n p u t t y p e = ’ t e x t ’ s i z e = ’ 4 0 ’ name = ’ s u j e t ’ / >< / t d >
< / t r >
< t r >< t h > M e s s a g e : < / t h >
< t d >< t e x t a r e a rows = ’ 2 0 ’ c o l s = ’ 4 0 ’ name = ’ m e s s a g e ’ >< / t e x t a r e a >
< / t d >
< / t r >
< / t a b l e >
< i n p u t t y p e = ’ s u b m i t ’ v a l u e = ’ E n v o y e r ’ / >
< / form>
À l’entrée du script, le processeur PHP décrypte les données provenant du formulaire et transférées dans le message HTTP, puis les place dans les tableaux
$_POST ou $_GET selon le mode de transmission choisi On peut également utiliser systématiquement le tableau $_REQUEST qui fusionne les deux précédents (plus le tableau $_COOKIES)
À ce stade, PHP peut effectuer ou non une transformation consistant à préfixer toutes les apostrophes simples ou doubles par la barre oblique inverse «\ » Ce
comportement est déterminé par l’option de configuration magic_quotes_gpc, et motivé par l’insertion fréquente des données provenant de formulaires dans des requêtes SQL Un des points épineux dans la manipulation de chaînes de caractères insérées ou lues dans une base MySQL est en effet la présence d’apostrophes Prenons l’exemple suivant :
INSERT INTO F i l m S i m p l e ( t i t r e , annee , n o m _ r e a l i s a t e u r ,
p r e n o m _ r e a l i s a t e u r , a n n e e _ n a i s s a n c e )
VALUES ( ’ L ’ o u r s ’ , 1 9 8 8 , ’ Annaud ’ , ’ J e a n−J a c q u e s ’ , 1943)
MySQL distingue les valeurs grâce aux apostrophes simples « ’ » Si une valeur contient elle-même une apostrophe, comme « L’ours » dans l’exemple ci-dessus, MySQL est perdu et produit un message d’erreur La bonne syntaxe est :
INSERT INTO F i l m S i m p l e ( t i t r e , annee , n o m _ r e a l i s a t e u r ,
p r e n o m _ r e a l i s a t e u r , a n n e e _ n a i s s a n c e )
VALUES ( ’ L\ ’ o u r s ’ , 1 9 8 8 , ’ Annaud ’ , ’ J e a n−J a c q u e s ’ , 1943)
La présence d’un caractère «\ » devant l’apostrophe (on parle «
d’échappe-ment ») permet à MySQL d’interpréter corrected’échappe-ment cette dernière comme faisant partie de la chaîne Quand l’option magic_quotes_gpc vaut On, un titre comme L’ours sera automatiquement représenté par la valeur L\’ours dans le script
recevant les données On pourra donc l’insérer tel quel dans une requête SQL Cependant, comme le montre l’application que nous sommes en train de créer, une chaîne de caractères peut être utilisée dans bien d’autres contextes que SQL, et
Trang 2« l’échappement » des apostrophes par des barres obliques devient inutile et gênant.
Il faut alors se poser sans cesse la question de la provenance de la variable, de la configuration courante de PHP, et de la nécessité ou non d’utiliser l’échappement C’est encore plus ennuyeux quand on écrit des fonctions puisqu’il faut déterminer si les paramètres peuvent ou non provenir d’une transmission HTTP, et si oui penser à uniformiser, dans les appels à la fonction, la règle d’échappement à utiliser
Depuis la parution de PHP 5, les concepteurs et distributeurs du langage semblent renoncer à cet échappement automatique Le problème est de risquer de se trouver dans une situation ó certain serveurs pratiquent l’échappement et d’autres non
Le seul moyen pour régler le problème une fois pour toutes et de normaliser
systé-matiquement les données HTTP La politique adoptée dans ce livre (vous êtes libre d’en inventer une autre bien entendu) consiste à tester, à l’entrée de tout script, si le mode d’échappement automatique est activé Si oui, on supprime cet échappement, pour toutes les chaỵnes transmises, avec la fonction stripSlashes() On pourra alors considérer par la suite que les données HTTP sont représentées normalement, comme n’importe quelle autre chaỵne de caractères manipulée dans le script Voici la fonction qui effectue cette opération sur chacun des tableaux contenant d’une part
des données transmises en mode get ou post, d’autre part des cookies.
Exemple 2.10 exemples/Normalisation.php: Traitement des tableaux pour supprimer l’échappement automatique
<? php
/ / A p p l i c a t i o n d e l a s u p p r e s s i o n d e s é c h a p p e m e n t s , s i n é c e s s a i r e , / / d a n s t o u s l e s t a b l e a u x c o n t e n a n t d e s d o n n é e s HTTP
r e q u i r e _ o n c e ( " N or m alisat ionH TTP php " ) ;
f u n c t i o n N o r m a l i s a t i o n ( )
{
/ / S i l ’ on e s t e n é c h a p p e m e n t a u t o m a t i q u e , on r e c t i f i e
i f ( g e t _ m a g i c _ q u o t e s _ g p c ( ) ) {
$_POST = N or m alis ationH TTP ( $_POST ) ;
$_GET = N or m alis at ionH TTP ( $_GET ) ;
$_REQUEST = NormalisationHTTP ($_REQUEST ) ;
$_COOKIE = NormalisationHTTP ( $_COOKIE ) ;
}
}
? >
La fonction get_magic_quotes_gpc() indique si l’échappement automatique est activé On parcourt alors les tableaux concernés et traite chaque valeur1 avec stripSlashes() qui supprime les «\ » Dans le parcours lui-même, il faut prendre
1 On ne traite pas la clé de chaque élément, en considérant qu’une clé ne devrait pas contenir
d’apostrophes.
Trang 3en compte le fait qu’un élément du tableau peut constituer lui-même un tableau imbriqué (cas par exemple d’un formulaire permettant de saisir plusieurs valeurs pour
un champ de même nom, voir page 46) Une manière simple et naturelle de parcourir les tableaux imbriqués sans se soucier du nombre de niveaux est d’appeler récursive-ment la fonction de normalisation NormalisationHTTP(), donnée ci-dessous
Exemple 2.11 exemples/NormalisationHTTP.php: Parcours récursif des tableaux pour appliquer stripSlashes().
<? php
/ / C e t t e f o n c t i o n s u p p r i m e t o u t é c h a p p e m e n t a u t o m a t i q u e
/ / d e s d o n n é e s HTTP d a n s un t a b l e a u d e d i m e n s i o n q u e l c o n q u e
f u n c t i o n NormalisationHTTP ( $ t a b l e a u )
{
/ / P a r c o u r s du t a b l e a u
f o r e a c h ( $ t a b l e a u a s $ c l e => $ v a l e u r )
{
i f ( ! i s _ a r r a y ( $ v a l e u r ) ) / / c ’ e s t un é l é m e n t : on a g i t
$ t a b l e a u [ $ c l e ] = s t r i p S l a s h e s ( $ v a l e u r ) ;
e l s e / / c ’ e s t un t a b l e a u : on a p p e l l e r é c u r s i v e m e n t
$ t a b l e a u [ $ c l e ] = NormalisationHTTP ( $ v a l e u r ) ; }
r e t u r n $ t a b l e a u ;
}
? >
La construction foreach utilisée ici est très pratique pour parcourir un tableau
en récupérant à la fois l’indice et la valeur de chaque entrée On peut noter que cette fonction prend en entrée un tableau et produit en sortie une copie dans laquelle les échappements éventuels ont été supprimés Il est possible, si l’on considère que ces copies sont pénalisantes, de traiter les paramètres par référence en les préfixant par
« & »
2.2.2 Contrôle des données HTTP
La seconde tâche à effectuer en recevant des données d’un formulaire est le contrôle des données reçues Cela signifie, au minimum,
1 le test de l’existence des données attendues,
2 un filtrage sur ces données, afin de supprimer des caractères parasites qui
pourraient infecter l’application;
3 et enfin le contrôle de quelques caractéristiques minimales sur les valeurs
On n’insistera jamais assez sur le fait qu’un script PHP est un programme que le monde entier peut appeler en lui passant n’importe quoi Bien entendu la majorité des utilisateurs du Web a bien autre chose à faire que d’essayer de casser votre application, mais il suffit d’un malveillant pour créer des problèmes, et de plus ces
Trang 4attaques sont malheureusement automatisables Un jour ou l’autre vous serez amenés
à vous poser la question de la robustesse de vos scripts Le filtrage des données en entrée, en particulier, est très important pour les sécuriser
La fonction ci-dessous est une version minimale des contrôles à effectuer Elle repose pour les contrôles sur les fonctions isSet() et empty() qui testent res-pectivement l’existence d’une variable et la présence d’une valeur (chaîne non vide) Pour le filtrage la fonction utilise htmlSpecialChars() qui remplace les caractères marquant une balise (soit « < », « > » et « & ») par un appel d’entité (soit, respectivement, <, > et &) On peut également envisager de supprimer totalement les balises avec la fonction strip_tags() L’injection de balises HTML dans les champs de formulaires est une technique classique d’attaque d’un site web
La fonction prend en entrée un tableau contenant les données, renvoie true
si elles sont validées, et false sinon Remarquer que le tableau $mail est passé par référence pour permettre sa modification suite au filtrage On pourra utiliser cette fonction en lui passant le tableau $_POST pour valider la saisie du formulaire précédent, ainsi que tout autre tableau dont on voudrait contrôler le contenu selon les mêmes règles Il est toujours préférable de concevoir des fonctions les plus indépendantes possibles d’un contexte d’utilisation particulier
Exemple 2.12 exemples/ControleMail.php:Ébauche de contrôle des données
<? php
/ / F o n c t i o n c o n t r ô l a n t l ’ e n t r é e d e l ’ a p p l i c a t i o n e−m a i l
f u n c t i o n C o n t r o l e M a i l (& $ m a i l )
{
/ / Le t a b l e a u e n p a r a m è t r e d o i t c o n t e n i r l e s e n t r é e s :
/ / d e s t i n a t a i r e , s u j e t e t m e s s a g e V é r i f i c a t i o n
i f ( ! i s S e t ( $ m a i l [ ’ d e s t i n a t a i r e ’ ] ) )
{ echo " P a s de d e s t i n a t a i r e ! " ; r e t u r n f a l s e ; }
e l s e $ m a i l [ ’ d e s t i n a t a i r e ’ ] = h t m l S p e c i a l C h a r s ( $ m a i l
[ ’ d e s t i n a t a i r e ’ ] ) ;
i f ( ! i s S e t ( $ m a i l [ ’ s u j e t ’ ] ) )
{ echo " P a s de s u j e t ! " ; r e t u r n f a l s e ; }
e l s e $ m a i l [ ’ s u j e t ’ ] = h t m l S p e c i a l C h a r s ( $ m a i l
[ ’ s u j e t ’ ] ) ;
i f ( ! i s S e t ( $ m a i l [ ’ m e s s a g e ’ ] ) )
{ echo " P a s de m e s s a g e ! " ; r e t u r n f a l s e ; }
e l s e $ m a i l [ ’ m e s s a g e ’ ] = h t m l S p e c i a l C h a r s ( $ m a i l [ ’ m e s s a g e ’ ] ) ;
/ / On v é r i f i e q u e l e s d o n n é e s n e s o n t p a s v i d e s
i f ( empty ( $ m a i l [ ’ d e s t i n a t a i r e ’ ] ) )
{ echo " D e s t i n a t a i r e v i d e ! " ; r e t u r n f a l s e ; }
i f ( empty ( $ m a i l [ ’ s u j e t ’ ] ) )
{ echo " S u j e t v i d e ! " ; r e t u r n f a l s e ; }
i f ( empty ( $ m a i l [ ’ m e s s a g e ’ ] ) )
{ echo " M e s s a g e v i d e ! " ; r e t u r n f a l s e ; }
Trang 5/ / M a i n t e n a n t on p e u t / d o i t é g a l e m e n t f a i r e d e s c o n t r ô l e s
/ / s u r l e s v a l e u r s a t t e n d u e s : d e s t i n a t a i r e , s u j e t , m e s s a g e / / V o i r l e s e x e r c i c e s p o u r d e s s u g g e s t i o n s
r e t u r n t r u e ;
}
? >
On pourrait envisager beaucoup d’autres contrôles à effectuer, certains étant décrits dans le document d’exercices disponible sur le site Les contrôles s’appuient fréquemment sur la vérification du format des données (comme, typiquement, l’adresse électronique) et nécessitent le recours aux expressions régulières qui seront présentées page 87
Dans toute la suite de ce livre, j’omets le plus souvent de surcharger le code par des contrôles répétitifs et nuisant à la clarté du code Le filtrage et le contrôle des données
en entrée font partie des impératifs de la réalisation d’un site sensible : reportez-vous
au site php.net pour des recommandations à jour sur la sécurité des applications PHP
2.2.3 Comment insérer dans la base de données : insertion dans MySQL
Voyons maintenant comment effectuer des insertions dans la base à partir des données reçues Il faut tout d’abord créer une table, ce qui se fait avec le script SQL suivant :
Exemple 2.13 exemples/Mail.sql:Création de la table stockant les e-mails
#
# C r é a t i o n d ’ une t a b l e p o u r s t o c k e r d e s e−m a i l s
#
CREATE TABLE M a i l ( i d _ m a i l INT AUTO_INCREMENT NOT NULL,
d e s t i n a t a i r e VARCHAR( 4 0 ) NOT NULL,
s u j e t VARCHAR( 4 0 ) NOT NULL,
m e s s a g e TEXT NOT NULL,
d a t e _ e n v o i DATETIME , PRIMARY KEY ( i d _ m a i l ) ) ;
Petite nouveauté : on trouve dans la table Mail une option AUTO_INCREMENT,
spécifique à MySQL Cette option permet d’incrémenter automatiquement l’attribut id_mail à chaque insertion De plus, cet attribut doit être déclaré comme clé primaire, ce qui signifie qu’il ne peut pas prendre deux fois la même valeur parmi
les lignes de la table On peut insérer une ligne dans Mail sans indiquer de valeur
pour id_mail, déterminée automatiquement par MySQL