Première rédaction de cet article le 1 février 2011
Il y a un débat récurrent à l'intersection du monde de la sécurité informatique et de celui du développement Web sur la meilleure façon de traiter les caractères « dangereux » lorsqu'ils sont entrés par un utilisateur dans un formulaire de saisie. Faut-il interdire ces caractères dangereux, n'autoriser que les caractères sûrs ou bien... ?
Prenons l'exemple d'un formulaire Web où
l'utilisateur doit taper son nom. Le problème de départ est que certains caractères sont spéciaux pour certains
environnements. Ainsi, l'apostrophe ferme les
chaînes de caractères en SQL et, sans
précautions particulières, une apostrophe dans un nom peut mener à une
injection SQL, comme illustré dans
un dessin fameux de XKCD. De
même, les chevrons sont spéciaux en
HTML et peuvent être utilisés pour faire des
attaques XSS, si un méchant tape comme nom
<script>do_something();</script>
.
Historiquement, la première défense contre ces attaques avait été
d'interdire les caractères « dangereux ». Si les données sont à un
moment ou à un autre transmises à un SGBD, on
interdit l'apostrophe. Si elles sont à un moment ou à un autre
affichées sur une page Web, on interdit les chevrons. Cette méthode
est aujourd'hui très critiquée et largement considérée comme peu sûre
car il est très difficile d'avoir une liste complète de tous les
caractères dangereux pour une application donnée. Une des premières
bogues du serveur HTTP Apache
était d'interdire la chaîne ..
dans les noms de
fichiers demandés (pour empêcher qu'un attaquant ne remonte plus haut
que la racine du site Web) en oubliant que ces noms étaient traités
par un analyseur qui acceptait les guillemets et laissait donc passer
."."/repertoire/ou/je/veux/aller
... Autre exemple, en SQL,
chaque SGBD rajoute quelques caractères spéciaux à la norme
SQL. Ainsi, MySQL considère les
guillemets comme caractères spéciaux au même
titre que l'apostrophe et il faut donc les filtrer également... Et
songez que le même nom entré dans un formulaire sera peut-être
transmis à un SGBD, sur une page Web, et à un shell Unix. La liste des caractères dangereux devient de plus en
plus difficile à établir.
Donc, l'approche « négative » (interdire les caractères dangereux) est très peu sûre. Elle est en outre trop violente dans certains cas. Ainsi, un nom comme « O'Reilly » ou « D'Alembert » est parfaitement légitime et il serait anormal de demander à ces personnes de changer de nom pour éviter les injections SQL... (Au passage, tout programmeur digne de ce nom devrait avoir lu l'excellent « Falsehoods Programmers Believe About Names » qui donne une idée de la variété des noms possibles pour les humains.)
Qu'en est-il de l'approche « positive », n'autoriser que des
caractères sûrs ? Alors que même le plus débutant des bricoleurs
PHP sait que l'approche négative est mauvaise,
l'approche positive est au contraire celle qui est généralement
recommandée. Par exemple, les fanas des expressions
rationnelles vont autoriser uniquement les noms
correspondant à l'expression [a-zA-Z]+
puis, après
quelques protestations d'utilisateurs nommés Robbe-Grillet ou Du Pont, passer à
[a-zA-Z -]+
. Cette approche est très sûre, car elle
ne nécessite pas d'avoir une liste exhaustive des caractères
dangereux. Elle est souvent nommée input
sanitization (par exemple dans le dessin de XKCD, qui la
conseille implicitement).
Mais elle est aussi très restrictive. Pour les noms de famille pris comme exemple, elle interdirait O'Reilly. Et, si on l'autorise, on se retrouve avec le problème de départ, la présence d'un caractère dangereux dans le nom. Les seuls cas où cette méthode positive marche bien, ce sont ceux où les valeurs à entrer dans le formulaire sont contraintes par des règles syntaxiques strictes. Par exemple, s'il s'agit, non plus de noms quelconques mais d'identificateurs formatés selon une certaine grammaire, alors la méthode positive est acceptable, à condition que le programmeur suive réellement la grammaire en question. Or, bien des programmeurs ne prennent jamais la peine de lire les spécifications et font donc du code qui restreint arbitrairement les termes possibles, par exemple dans les adresses de courrier.
En pratique, la méthode positive se heurte donc vite à des
limites. C'est encore plus net si on prend en compte
Unicode. Car, si on a des utilisateurs nommés
Skłodowska ou Liétard, ASCII ne suffit
plus. On peut passer aux expressions Unicode (si le moteur
d'expressions rationnelles que vous utilisez les gèrent, ce qui, en
2011, devrait être la règle) et on écrit quelque chose comme
[\p{L}\p{Zs}-']+
(où p{L}
est
l'ensemble des lettres). Mais la liste des caractères ayant la
propriété L (pour « lettre ») est tellement longue (la grande majorité
des caractères Unicode sont des lettres, plus de 100 000 dans la
version 6) qu'il serait
imprudent de jurer qu'il n'y en a pas un dans le lot qui soit
dangereux dans certaines circonstances... (Déjà, dans mon expression,
il y a l'apostrophe.)
Que reste-t-il comme solution ? Elle est plus difficile et peut dans certains cas être moins sûre mais la seule méthode correcte est de ne pas filtrer en entrée mais en sortie. À part pour les cas où on peut s'appuyer sur une norme précise pour définir un jeu de caractères autorisé et où ce jeu ne comporte aucun caractère dangereux pour aucun des logiciels qu'on utilisera, c'est la bonne approche. Il faut donc accepter tout ce qui correspond à la sémantique du champ en question, puis contrôler l'utilisation de ces données au moment de l'exportation (vers SQL, vers PDF, vers LaTeX, vers HTML, etc). C'est une simple application du principe de robustesse (« soyez tolérant dans ce que vous acceptez et prudent dans ce que vous envoyez »).
Pour faciliter la tâche du programmeur, et pour diminuer les risques d'oubli, il est bien sûr recommandé de se servir pour ce contrôle de mécanismes de gabarits comme les requêtes préparées en SQL ou comme des gabarits XML, par exemple TAL.
Merci à Pierre Beyssac, Ollivier Robert et Bertrand Petit pour leurs conseils avisés.
Version PDF de cette page (mais vous pouvez aussi imprimer depuis votre navigateur, il y a une feuille de style prévue pour cela)
Source XML de cette page (cette page est distribuée sous les termes de la licence GFDL)