Sécurité des applications Web - Menaces et contre-mesures

Auteur: Mohamed CHINY Durée necessaire pour le cours de Sécurité des applications Web - Menaces et contre-mesures Niveau recommandé pour le cours de Sécurité des applications Web - Menaces et contre-mesures Supports vidéo non disponibles pour ce cours Exercices de renforcement non disponibles pour ce cours Quiz non disponibles pour ce cours

Leçon 4: Vulnérabilité XSS (Cross Site Scripting): injection de JavaScript côté client

Toutes les leçons

Le Cross Site Scripting - XSS

Définition

L'attaque XSS (pour Cross Site Scripting) est une attaque très populaire presque au même titre que l'injection SQL. Elle est également présente dans le podium (à dix marches) d'OWASP en 2017.

L'attaque XSS vise comme cible le client plutôt que le serveur. Elle se sert d'un script Javascript qui sera exécuté chez le client pour détourner le fonctionnement de son navigateur. En effet, le pirate développe un script Javascript selon ses attentions, il soumet ensuite ce script comme étant une chaine de caractères à un serveur via une de ses entrées (formulaire, URL...). Si le serveur présente une vulnérabilité vis-à-vis du XSS, alors le script sera accepté et probablement déposé dans la base de données (ou autre forme de source de données). Jusqu'ici il ne se passe rien de spécial. Mais imaginez qu'à un certain moment un client se connecte sur le serveur et demande une page qui affiche les entrées de la base de données, et par hasard c'est le contenu Javascript qui sera envoyé au navigateur. Puisque Javascript est un langage coté client, alors il sera aussitôt exécuté sur le navigateur du client et fera ce qui a été demandé par le pirate.

Dans ce cas de figure, n'importe quel client peut être victime de cette attaque. Tout dépend de qui a demandé l'affichage du contenu de la base de données qui coïncide avec le script. Par contre, si le pirate veut viser un client en particulier alors il peut lui envoyer un message privée encapsulant une attaque XSS via un site Web (présentant la vulnérabilité) comme un forum de discussion ou autre.

Une attaque XSS peut également provoquer des dommages parfois conséquents comme par exemple:
  • Rediriger un utilisateur à son insu vers un site pirate ou site compromettant
  • Afficher des messages indésirables sur le navigateurs du client
  • Empêcher l'exécution normale des scripts embarqués dans la page
  • Ordonner le déclenchement de périphériques sur l'ordinateur de la victime comme la Webcam

Il y a quelques années, les pirates pouvaient même voler les cookies de la victime à l'aide de l'attaque XSS et usurper ainsi son identité. Heureusement, une telle action n'est plus possible.

Exploitation

Comme le principe est de poser un script javascript sur le serveur et attendre à ce qu'un client le charge et l'exécute à son insu, alors le pirate commence par écrire un bout de code javascript.

Exemple:
<script>
   window.location="http://www.site_dangereux_et_compromettant.com";
</script>
Ensuite, le pirate dépose ce code sur une des entrées d'un site Web (qui est supposé contenir la vulnérabilité) puis poste le tout. Désormais le code est présent sur la base de données et ne fait rien.

Il suffit maintenant qu'un client (la victime) se connecte au site et demande l'affichage des entrées (ou quelques unes) de la base de données. Si le script fait partie des entrées demandées, le navigateur du client sera immédiatement redirigé vers le site pirate.
Dans certains cas, le pirate peut ajouter un chevron de fermeture de balise (>) ainsi que des guillemets (") ou des apostrophes ('), selon la syntaxe du code, afin que l’injection XSS soit correctement interprétée et devienne efficace. Par exemple "><script>alert("XSS")</script>.

Variantes du XXS: Reflected XSS et Stored XSS

Le Reflected XSS est une variante de la vulnérabilité XSS qui survient au moment de l'injection d'un code malveillant via les entrées de l'application Web et qui donne lieu à l'exécution de ce code-là au moment même sur le navigateur. Cette variante d'XSS n'est pas très dangereuse car elle s'exécute sur le navigateur du pirate lui-même et ne risque pas de s'exécuter sur le navigateur d'une personne tierce.

Le Stored XSS par contre est plus dangereuse, car le pirate injecte le code malveillant dans l'application Web et celle-ci le stocke en dur (dans une base de données par exemple). Ce code-là attend le moment qu'il soit chargé par une victime puis s'exécute sur le navigateur de celle-ci.
Il existe aussi une variante nommée DOM‑Based XSS où le code malveillant n’est pas renvoyé par le serveur, mais exécuté directement dans le navigateur à cause du code JavaScript de la page. Si ce code lit des données de l’URL ou du navigateur et les affiche sans vérification, un pirate peut y insérer du code qui sera interprété par le navigateur.

Comment s'en protéger?

Les mesures de sécurité que nous allons voir seront implémentés au niveau du serveur et non pas sur le client. Donc, si le serveur est sécurisé contre les attaques XSS, ses clients n'auront pas à affronter des ennuis comme ce qui a été expliqué dans le paragraphe précédent.

Au niveau du code PHP

Comme pour les injections SQL, l'attaque XSS est dues à des entrées provenant de l'extérieur (donc non fiables). La solution consiste donc à filtrer les entrées de l'utilisateur en appliquant les fonctions comme addslashes() ou strip_tags() qui supprime toutes les balises contenues dans la chaine entrée. On peut aussi formater les mots-clés HTML à l'aide de la fonction htmlentities() ou htmlspecialchars(). Cependant, une chaine de caractères faisant office d'un script XSS est généralement longue, il faut alors n'autoriser qu'une certaine longueur maximale pour les entrées en la vérifiant à l'aide de la fonction strlen() ou en la tronquant systématiquement à l'aide la fonction substr().

Au niveau de la configuration du serveur

Comme pour l'injection SQL, on peut activer la directive magic_quotes_gpc dans le fichier php.ini pour échapper automatiquement tous les caractères spéciaux (notamment les simples et doubles quotes) figurants dans les chaines provenant le l'extérieur. Bien que ce n'est pas suffisant, mais cette solution apportera un peu d'aide quand même.
magic_quotes_gpc = On
La directive magic_quotes_gpc a été déclarée obsolète en PHP 7.4 et supprimée définitivement en PHP 8.0. Elle n’est donc plus disponible dans les versions modernes du langage, car elle donnait une fausse impression de sécurité, surtout contre les injections SQL comme on l'a vu dans la leçon dédiée à cette vulnérabilité.

Appliquer le mécanisme Content Security Policy (CSP)

Le Content Security Policy (CSP) est une mesure de sécurité web qui permet de contrôler quelles ressources (scripts, images, styles...) peuvent être chargées et exécutées par une page.

En définissant des règles dans l’en‑tête HTTP, le développeur limite l’exécution de contenus non autorisés et réduit ainsi les risques d’attaques comme le XSS ou l’injection de scripts externes. En bref, le CSP agit comme un pare‑feu côté navigateur, empêchant l’exécution de code malveillant provenant de sources non approuvées.

Le Content Security Policy (CSP) fonctionne grâce à un en‑tête HTTP envoyé par le serveur. Cet en‑tête contient des règles qui indiquent quelles ressources sont autorisées. Le navigateur reçoit cet en‑tête et applique les règles, empêchant ainsi l’exécution de contenus non approuvés.

Le Content Security Policy (CSP) peut être appliqué de deux manières: soit en configurant le serveur web pour qu’il envoie l’en‑tête de sécurité (par exemple via le fichier .htaccess sur Apache), soit directement depuis le code de l’application, en utilisant des fonctions comme header() en PHP.

Spécifier un entête CSP via .htaccess (Apache)


On peut par exemple mettre ce code dans le fichier .htaccess (que l'on doit impérativement placer dans la racine du projet):
<IfModule mod_headers.c>
   Header set Content-Security-Policy "script-src 'self' 'nonce-123'; object-src 'none';"
</IfModule>

Explication rapide de ce bloc de configuration Apache (via .htaccess):
  • <IfModule mod_headers.c>: a directive ne sera exécutée que si le module mod_headers est activé sur Apache (c’est lui qui permet de gérer les en‑têtes HTTP).
  • Header set Content-Security-Policy: ajoute l’en‑tête CSP à toutes les réponses qui proviennent du serveur.
  • script-src 'self' 'nonce-123': autorise uniquement les scripts provenant du même domaine ('self') et ceux qui portent le nonce 123.
  • object-src 'none': interdit complètement le chargement d’objets externes (comme Flash ou autres plugins).

Spécifier un entête CSP pour le serveur Nginx


Dans Nginx, on place la directive add_header dans le bloc de configuration qui correspond au site (ou partie de l'application) qu'on veut protéger:
server {
   listen 80;
   server_name exemple.com;
   location / {
      add_header Content-Security-Policy "script-src 'self' 'nonce-123'; object-src 'none';";
   }
}
L’implémentation du Content Security Policy dans Nginx ressemble beaucoup à celle d’Apache, mis à part quelques petits détails relatifs à l'environnement (Apache ou Nginx). Mais dans les deux cas, le principe est identique: le serveur envoie l’en‑tête CSP au navigateur qui applique ensuite les règles pour contrôler quelles ressources sont autorisées et bloquer les scripts non approuvés.

Spécifier un entête CSP via un entête envoyé par le code PHP


En PHP, on peut envoyer un en‑tête Content Security Policy (CSP) directement depuis notre code avec la fonction header() comme ceci:
header("Content-Security-Policy: script-src 'self' 'nonce-123'; object-src 'none';);
Là encore, le code est très similaires à ce qu'on a vu pour Apache ou Nginx, sauf que le CSP a été appliqué en envoyant directement l'entête via la fonction native header de PHP.

Un avantage majeur de générer le CSP via PHP est la possibilité de produire un nonce aléatoire cryptographiquement sûr à chaque requête. Grâce à des fonctions comme random_bytes(), le serveur génère une valeur imprévisible et unique qui est ensuite insérée dans l’en‑tête CSP et dans les balises <script>. Ainsi, seul le code muni du bon nonce est autorisé à s’exécuter. Cette approche rend l’exploitation d’une injection XSS beaucoup plus difficile, car un attaquant ne peut pas deviner ou réutiliser le nonce.

Exemple:
$random=random_bytes(16);
$nonce=base64_encode($random);
header("Content-Security-Policy: script-src 'self' 'nonce-$nonce'; object-src 'none';);

Si maintenant on veut exécuter du code Javascript inline légitime en spécifiant la valeur du nonce généré en PHP:
<script nonce="<?=$nonce?>">
   document.body.onload=function(){
      alert("Page chargée avec succès");
   }
</script>

Il est fortement conseillé d’utiliser des nombres aléatoires cryptographiquement sûrs (générés par des fonctions comme random_bytes() en PHP) pour créer des nonces ou des jetons de sécurité (tokens). Contrairement aux générateurs pseudo‑aléatoires classiques comme rand() ou mt_rand() qui produisent des séquences prévisibles et donc vulnérables aux attaques, random_bytes() s’appuie sur des sources d’entropie du système et garantit une imprévisibilité robuste.