Nous avons déjà parlé dans le chapitre sur les chaînes de caractères de la protection des chaînes ("Protéger les chaînes") quand elles doivent être affichées par le navigateur.

Quand nous affichons des chaînes littérales que nous avons nous-même inclus dans notre code (message d'erreur, label de champ de formulaire, texte explicatif, etc), nous somme sûrs qu'elles ne contiennent pas de code HTML ou JavaScript que nous ne voudrions pas voir exécuter par le navigateur quand il va faire l'affichage de ces chaînes.

Il n'en va pas de même quand nous affichons des chaînes dont nous ne maîtrisons pas totalement la provenance ou la composition. C'est particulièrement vrai pour ce qui est stocké dans une base de données, d'autant plus que la philosophioe du "Web 2.0" est une participation accrue des utilisateurs qui sont sollicités pour laisser des messages, des avis, des commentaires, etc.

Rien n'empêche ces utilisateurs si ils ont des connaissances en HTML ou JavaScript d'inclure des tags ou des instructions dans les textes qu'ils laissent sur telle ou telle site. Si rien n'est fait pour surveiller et inhiber ces éléments de langage, ils seront tout simplement "exécutés" par le navigateur, ce qui peut conduire à des failles de sécurité dans notre site, ou à ce que d'autres utilisateurs soient piégés.

L'exemple suivant illustre du code malicieux saisi dans du texte et stocké dans la base de données.

Exemple : texte avec du code interprété à l'affichage
<?php ob_start(); require('bib_params.php'); require('bib_fonctions.php'); htmlDebut('Sélection simple : le livre no 4'); $bd = bdConnecter(); //-- Requête ---------------------------------------- $sql = 'SELECT * FROM livres WHERE liID = 4'; $r = mysqli_query($bd, $sql) or bdErreur($bd, $sql); //-- Traitement ------------------------------------- $enr = mysqli_fetch_assoc($r); // Libération de la mémoire associée au résultat de la requête mysqli_free_result($r); echo 'Titre : ', $enr['liTitre'], '<br>Résumé : ', $enr['liResume'], '<br>Nb Pages : ', $enr['liPages'], '<br>Prix : ', $enr['liPrix']; //-- Déconnexion ------------------------------------ mysqli_close($bd); htmlFin(); ?>

Le responsable de cette "redirection" vers un site pirate imitant la page de login de Moodle est simplement le morceau de code suivant glissé dans le résumé du livre :
<script>location = "../exemple/login.html"</script>

Il ne faut pas croire que seul JavaScript est capable de faire des dégâts. Un simple lien HTML (<a>) peut se révéler aussi dangereux car le click peut nous emmener autre part que ce qui est indiqué. Un tag <img> peut aussi pointer non sur une image, mais sur un script PHP qui pourra par exemple obtenir des informations sur les cookies de session avant de renvoyer (ou pas) une image.

Pour éviter que du code soit interprété par le navigateur sans que nous le voulions, il faut protéger toutes les chaînes de caractères qui viennent de la base de données avant d'en faire l'affichage.

Nous pouvons utiliser 3 fonctions :

- htmlentities() qui remplace les caractères de la syntaxe HTML et tous les caractères accentués par leur entité HTML,
- htmlspecialchars() qui remplace seulement les caractères de la syntaxe HTML par leur entité HTML,
- strip_tags() qui supprime tous les tags HTML contenu dans un texte.

Exemple : texte avec htmlentities
<?php ob_start(); require('bib_params.php'); require('bib_fonctions.php'); htmlDebut('Sélection simple : le livre no 4'); $bd = bdConnecter(); //-- Requête ---------------------------------------- $sql = 'SELECT * FROM livres WHERE liID = 4'; $r = mysqli_query($bd, $sql) or bdErreur($bd, $sql); //-- Traitement ------------------------------------- $enr = mysqli_fetch_assoc($r); // Libération de la mémoire associée au résultat de la requête mysqli_free_result($r); echo 'Titre : ', htmlentities($enr['liTitre'], ENT_COMPAT, 'ISO-8859-1'), '<br>Résumé : ', htmlentities($enr['liResume'], ENT_COMPAT, 'ISO-8859-1'), '<br>Nb Pages : ', $enr['liPages'], '<br>Prix : ', $enr['liPrix']; //-- Déconnexion ------------------------------------ mysqli_close($bd); htmlFin(); ?>

On voit que maintenant le code HTML et JavaScript inclus dans le texte du résumé n'est plus exécuté, mais affiché tel qu'il a été saisi puisque htmlentities() a transformé les < et > syntaxiques du langage HTML en ... entités HTML, c'est à dire en simples &lt; et &gt;. Ainsi, le navigateur ne reçoit plus la chaîne '<script>' (qui signifie pour lui balise ouvrante d'un élément SCRIPT) mais la chaîne '&lt;script&gt;' et il affiche simplement cette chaîne en remplaçant les entités HTML par les caractères correspondant (l'entité &lt; est par exemple remplacé par un simple caractère "inférieur".

Je vous invite à regarder le code source de la page obtenue lors de l'exécution du script précédent, vous y retouverez notamment la chaîne '&lt;script&gt;'.

Exemple : texte avec strip_tags
<?php ob_start(); require('bib_params.php'); require('bib_fonctions.php'); htmlDebut('Sélection simple : le livre no 4'); $bd = bdConnecter(); //-- Requête ---------------------------------------- $sql = 'SELECT * FROM livres WHERE liID = 4'; $r = mysqli_query($bd, $sql) or bdErreur($bd, $sql); //-- Traitement ------------------------------------- $enr = mysqli_fetch_assoc($r); // Libération de la mémoire associée au résultat de la requête mysqli_free_result($r); echo 'Titre : ', strip_tags($enr['liTitre']), '<br>Résumé : ', strip_tags($enr['liResume']), '<br>Nb Pages : ', $enr['liPages'], '<br>Prix : ', $enr['liPrix']; //-- Déconnexion ------------------------------------ mysqli_close($bd); htmlFin(); ?>

strip_tags() a retiré les tags contenus dans le résumé du livre, ne laissant plus apparaître que l'instruction JavaScript qui est devenue inopérante puisqu'elle n'est plus entourées de tags indiquant au navigateur de l'exécuter.

On privilégiera htmlentities() pour protéger les chaînes avant affichage car cette fonction fait aussi le remplacement des caractères accentués.

Le recours à une fonction pour faire le travail à notre place est intéressant car utiliser htmlentities() pour toutes les sorties peut s'avérer pénible du fait de la répétition du code et des oublis qui peuvent arriver.

Une telle fonction est facile à écrire et à placer dans notre bibliothéque bib_fonctions.php :

    /**
     * Protection HTML des chaînes contenues dans un tableau
     *
     * @param array		$tab	Tableau des chaînes à protéger
     */
    function htmlProteger(&$tab) {
    	foreach ($tab as $cle => &$val) {
    		$val = htmlentities($val, ENT_COMPAT, 'ISO-8859-1');
    	}
    }

Remarques :

- le tableau est transmis à la fonction par référence pour qu'elle soit capable de le modifier (ie les modifications effectuées dans la fonction sont effectuées directement sur le tableau transmis lors de son appel).
- Dans la boucle foreach, $val reçoit une référence sur la valeur de l'élément "parcouru" afin que l'on puisse modifier cette valeur en utilisant $val.
  On aurait aussi pû aussi définir cette fonction comme indiqué ci-dessous :

      function htmlProteger(&$tab) {
      	foreach ($tab as $cle => $val) {
      		$tab[$cle] = htmlentities($val, ENT_COMPAT, 'ISO-8859-1');
      	}
      }

  Mais, avec cette deuxième solution, la boucle foreach réalise une copie du tableau (relisez le chapitre consacré aux 'Boucles avec foreach' si nécessaire). Donc la première solution permet d'économiser de la mémoire.
- Ici, il n'est pas nécessaire de faire un unset($val) à la fin de fonction car justement la variable $val est automatiquement détruite lorsque la fonction se termine.

Exemple : affichage avec notre fonction de protection
<?php ob_start(); require('bib_params.php'); require('bib_fonctions.php'); htmlDebut('Sélection simple : le livre no 4'); $bd = bdConnecter(); //-- Requête ---------------------------------------- $sql = 'SELECT * FROM livres WHERE liID = 4'; $r = mysqli_query($bd, $sql) or bdErreur($bd, $sql); //-- Traitement ------------------------------------- $enr = mysqli_fetch_assoc($r); // Libération de la mémoire associée au résultat de la requête mysqli_free_result($r); htmlProteger($enr); echo 'Titre : ', $enr['liTitre'], '<br>Résumé : ', $enr['liResume'], '<br>Nb Pages : ', $enr['liPages'], '<br>Prix : ', $enr['liPrix']; //-- Déconnexion ------------------------------------ mysqli_close($bd); htmlFin(); ?>