Audit de sécurité : 2 - XSS Stored
L'audit de sécurité a révélé une faille de type XSS Stored. C'est une faille majeure.
L'application de base
Nous avons développé un formulaire maison via le Form API de Drupal 7.
Ce formulaire comporte une étape de validation (hook_form_validate()) et une étape d'envoi (hook_form_submit()).
Le hook_form_validate() nous permet de vérifier l'intégrité des données saisies avant envoi et de demander, le cas échéant, la correction des erreurs à l'utilisateur.
Le hook_form_submit() met en place un mécanisme de création de node basé sur les informations rentrées dans le formulaire par l'utilisateur.
Côté administration du site, il est possible de retrouver chaque résultat du formulaire (les données saisies) en consultant le node associé.
Exploitation
Cross-site scripting (XSS) est une attaque par injection de code JavaScript ou HTML. Elle permet d'exécuter du code malicieux dans le navigateur de la victime. L'attaquant ne vise pas directement sa victime, il exploite la vulnérabilité d'un site web pour injecter un code malveillant. Le pirate profite de la confiance de la victime envers le site web, le code malicieux semble être légitime. Il existe deux types d'XSS :
- XSS persistante : il s'agit de l'attaque la plus dangereuse. Un attaquant peut injecter un code dans le serveur du site vulnérable et ainsi infecté toutes les personnes visitant cette page.
- XSS non persistante : ce type d'XSS est beaucoup plus répandu. Le script de l'attaquant doit faire partie de la requête de la victime pour être exécuté.
Dans notre cas, l'audit de sécurité a détecté une XSS de type persistante. Si l'attaquant envoie une requête forgée, il peut exécuter du code JavaScript.
Exemple
Voici un exemple d'exploitation de l'injection XSS dans un des champs du formulaire :
<sript>alert('hello');</script>
Ici, rien de bien compliqué en matière de code. L'attaquant demande simplement au navigateur d'afficher le texte "hello" dans une fenêtre de dialogue. Mais nous pouvons imaginer des scénarios beaucoup plus élaborés avec des conséquences beaucoup plus néfastes pour la personne affichant ce bout de code. Par exemple, il serait possible d'appeler directement un fichier JavaScript distant comportant le code à exécuter.
Préconisation
Pour se protéger d'une faille XSS, il est important de filtrer et nettoyer tout élément provenant d'un utilisateur de l'application. Par exemple, il est recommandé de retirer toutes les balises HTML des chaînes envoyées par un utilisateur afin que celles-ci ne puissent pas être exécutées une fois réaffichées par le site.
Solution(s)
Je fus d'abord surpris de la découverte de cette faille. En effet, je pensais (naïvement) que Drupal gérait nativement le filtrage des données saisies sur un formulaire maison via son Form API.
Et bien non !!!
Il a donc fallu élaborer une stratégie de filtrage des données saisies. Après quelques recherches, je me suis penché vers les fonctions d'assainissement : Sanitization functions
Et j'y ai trouvé mon bonheur, principalement grâce aux fonctions filter_xss() et check_markup().
Une fois ces deux fonctions mises en place, les données envoyées sont assainies et le XSS n'est plus possible.
Conclusion
Malgré le côté "majeure" de la faille, celle-ci fut assez facile à corriger et peu onéreuse en temps (2 lignes de code à rajouter).
Mais cette faille m'a aussi rappelé que rien n'est acquis. À force de développer sur un C.M.S. nous avons tendance à croire que celui-ci va s'occuper de tout (sécurité, traitement des données, faire la vaisselle...). Et ce n'est pas toujours vrai. Du coup, on en vient à oublier le minimum de base côté sécurité.
Les tests d'intrusions sur un formulaire maison restent le meilleur moyen de vérifier que l'on a bien pensé à ce minimum !