Qu'est-ce qu'une attaque par scripts intersites (XSS) ?
Vous recevez un e-mail vous annonçant que votre blogueur préféré vient de publier un nouvel article. Vous cliquez donc sur le lien comme d'habitude et vous vous mettez à lire. L'article est tellement formidable que vous tenez à féliciter l'auteur en lui laissant un commentaire. Pour ce faire, vous devez d'abord vous identifier sur le site. Vous cliquez donc sur le bouton de connexion, vous saisissez vos identifiants et... vous retrouvez de nouveau sur l'écran de connexion. Bon, vous avez dû faire une erreur en saisissant votre mot de passe. Vous saisissez de nouveau vos identifiants, et cette fois-ci vous arrivez bien sur la fenêtre permettant de publier un commentaire. Eh bien, vous vous dites que vous avez dû faire une faute de frappe lors de votre premier essai, ce ne serait pas la première fois.
Le lendemain, vous découvrez que vous avez été banni du site, au motif que vous auriez publié de nombreux commentaires choquants qui ont violé les conditions générales d'utilisation. Vous avez aussi perdu accès à vos comptes Facebook, Twitter et LinkedIn et pire encore, votre compte bancaire semble vide.
Qu'a-t-il bien pu se passer ? Est-ce qu'une caméra espion pointée sur votre poste de travail vous a observé saisir vos mots de passe ? Votre téléphone a-t-il été piraté et toutes vos saisies interceptées ? Non, la réponse est bien plus simple que ça.
Vous avez été victime d'une attaque par scripts intersites (XSS). Sans le savoir, vous avez aggravé les choses : comme vous n'arriviez pas à vous souvenir de tous vos mots de passe et n'utilisez pas de gestionnaire dédié, vous avez naïvement utilisé le même mot de passe pour vos comptes Facebook, Twitter et LinkedIn, ainsi que pour votre compte sur le site du bloggeur. Vous avez aussi gardé le même mot de passe pour le site de votre banque, ce qui explique pourquoi votre compte se retrouve vide.
Une attaque XSS est une technique malveillante qui permet à un pirate d'exécuter du code JavaScript (ou d'un autre langage de script) dans le navigateur d'un autre utilisateur. C'est aussi le type d'attaque le plus courant et un que les développeurs négligent souvent. Nous allons nous pencher sur son fonctionnement et vous expliquer comment vous pouvez protéger vos applications.
Une attaque XSS ne cible pas directement une victime en particulier, mais tous les visiteurs d'un site Web donné. En effet, le pirate exploite les vulnérabilités dans la sécurité d'un site pour faire exécuter du code malveillant à ses visiteurs. Ce code malveillant semble constituer une partie valide du site Web et est donc envoyé à tout utilisateur voulant accéder à la page. Pour ainsi dire, le site Web est le complice involontaire du pirate.
Comment cela fonctionne ?
Le seul moyen pour le pirate d'exécuter du code malveillant dans le navigateur de sa victime consiste à l’injecter dans l'une des pages que cette dernière télécharge depuis le site Web. Il peut y parvenir si la page en question inclut un formulaire de saisie. Le pirate peut alors insérer une chaîne qui sera traitée comme du code par le navigateur de la victime.
Les pages contenant des articles d'actualité ou de blog et permettant aux visiteurs de laisser des commentaires, comme dans l'exemple que nous venons de donner, constituent un lieu idéal pour les attaques XSS. Reprenons : un pirate a accédé au site et a injecté du code JavaScript malveillant dans un commentaire qu'il a publié. Ensuite, lorsque vous avez accédé au site, ce code malveillant a été exécuté au moment du rendu de la page dans votre navigateur. Le code a modifié le bouton de connexion figurant sur la page. Ainsi, lorsque vous avez cliqué dessus, vous avez été redirigé vers une page imitant la page de connexion d'origine du site. C'est là que vous avez saisi vos identifiants, avant d'être redirigé vers le véritable site. L'opération était imperceptible, vous avez cru avoir simplement fait une erreur lors de la saisie de vos identifiants. Cette redirection était en réalité une deuxième attaque, appelée redirection ouverte, qui a été injectée dans le site à l'aide de la première attaque par scripts intersites. Nous y reviendrons plus en détail dans le chapitre suivant.
Quelles sont les conséquences d'une telle attaque ?
Comment une telle attaque est-elle possible ? Le JavaScript n'est-il pas censé s'exécuter dans un environnement fermé au sein des navigateurs et ne disposer que d'un accès minimal aux fichiers et au système d'exploitation de la machine ? Les navigateurs disposent de fenêtres de console vous permettant d'exécuter du code directement sur une page Web. Alors, pourquoi cette attaque est-elle si grave ? Ne serions-nous pas en train d'exagérer ? Ce scénario est-il vraiment réaliste ?
C'est vrai, le code JavaScript s'exécute dans un environnement très restreint. Néanmoins, il peut tout de même réaliser diverses actions au sein du navigateur.
Tout d'abord, il a accès aux cookies enregistrés sur l'ordinateur d'un utilisateur. Ces fichiers peuvent contenir des informations sensibles. Un pirate peut par exemple utiliser document.cookie pour accéder aux cookies de la victime associés au site Web, les envoyer sur son propre serveur et en extraire des informations sensibles pour faire croire au serveur que ses requêtes proviennent d'une source valide. Une petite usurpation d'identité, en somme.
Ensuite, le JavaScript peut aussi envoyer des requêtes HTTP à presque n'importe quelle destination et avec n'importe quel type de contenu. Ces requêtes peuvent notamment permettre l'enregistrement de certains outils JavaScript dans la page, comme des écouteurs. Un pirate pourrait par exemple enregistrer un écouteur d'événement de clavier avec addEventListener. Cet écouteur va alors capturer toutes les saisies de l'utilisateur et les envoyer sur le serveur du pirate. Cette technique est souvent utilisée pour voler des mots de passe et numéros de carte bancaire.
Enfin, le JavaScript permet aussi de manipuler le DOM afin de modifier le code HTML de la page en cours en y ajoutant des éléments pouvant inciter l'utilisateur à fournir des données sensibles. Par exemple, un pirate peut insérer un faux formulaire de connexion dans la page, dont l'attribut action cible son propre serveur. L'utilisateur lui enverra ainsi directement ses identifiants.
Ce type d'attaque est parfois difficile à repérer, car le script malveillant s'exécute sur le site légitime et non sur un clone. Le script est traité comme le reste du code et des données distribués par le site. Il a accès aux cookies associés au site, et le nom d'hôte indiqué dans l'URL est bien celui du site Web d'origine. Ainsi, le script malveillant est considéré comme un élément valide du site Web et peut donc réaliser toutes les actions que le site Web peut lui-même effectuer.
Notre objectif étant de vous apprendre comment protéger vos applications des attaques avec .NET Core et non de vous transformer en hacker de haut vol, commençons par voir comment empêcher les attaques XSS.
Protégez vos applications contre les attaques XSS
Il existe deux moyens de protéger vos applications des attaques XSS :
Encoder les données avant leur affichage pour que le navigateur les interprète simplement comme des données, mais pas comme du code.
Valider les données lors de leur saisie pour vous assurer qu'elles ne contiennent aucune commande malveillante lors de leur stockage.
Encodage
En règle générale, pour vous protéger de la plupart des attaques de ce type, n'intégrez JAMAIS des données non fiables dans votre code HTML. Mais qu'entend-on exactement par "données non fiables" ? Les données non fiables sont toutes les données pouvant être falsifiées par un pirate. Il peut s'agir de données saisies dans des formulaires HTML, de chaînes de requête, d'en-têtes HTTP et même de données extraites d'une base de données.
Si vous devez accepter des données non fiables, voici quelques règles de base à suivre pour protéger vos sites contre les attaques XSS et par suite rendre vos données fiables.
Avant de placer des données non fiables dans un élément HTML, assurez-vous que tous les caractères spéciaux HTML de ces données sont encodés. Cet encodage transforme les caractères tels que
<
en une forme sûre (comme<
).Avant de placer des données non fiables dans un attribut HTML, assurez-vous que ces données sont elles aussi encodées. L'encodage des attributs englobe l'encodage des caractères spéciaux HTML et inclut des caractères supplémentaires, notamment
"
et'
.Avant de placer des données non fiables dans du code JavaScript, insérez-les d'abord dans un élément HTML dont vous récupérerez le contenu lors de l'exécution. Si cela n'est pas possible, assurez-vous que les caractères spéciaux JavaScript de ces données sont encodés. Ce type d'encodage remplace les caractères dangereux (
<
, par exemple) par leur équivalent hexadécimale.Avant de placer des données non fiables dans une chaîne de requête d'URL, assurez-vous de les encoder au format pourcent.
En d'autres termes, veillez à encoder toutes les données non fiables en fonction de leur destination au sein de la page.
Alors, comment encoder les données ?
Encodez les caractères spéciaux HTML avec Razor
Heureusement, le moteur Razor utilisé dans MVC encode automatiquement toutes les sorties issues de variables. En fait, vous devez travailler très dur pour l'en empêcher. Dès que vous utilisez la directive @
pour accéder aux données, Razor applique des règles d'encodage des attributs HTML. Étant donné que l'encodage des attributs englobe l'encodage des caractères spéciaux, il n'est pas nécessaire de se demander si vous devez utiliser l'encodage des caractères spéciaux HTML ou l'encodage des attributs pour tel ou tel élément de données : Razor s'en occupe automatiquement.
Prenons l'exemple de code Razor ci-dessous :
@{
var donneesNonFiables = "<\"Source non fiable\">";
}
@donneesNonFiables
Le code ci-dessus affiche le contenu de la variable donneesNonFiables
. Vous remarquerez que cette variable contient les caractères <
, "
et >
, qui sont tous utilisés dans les attaques XSS. Pourtant, si vous examinez la source HTML de la page générée par ce code, vous verrez le texte suivant :
<"Source%20non%20fiable">
Encodez JavaScript avec Razor
Parfois, il est nécessaire d'insérer des données dans du code JavaScript afin de les traiter dans votre vue. Vous pouvez le faire de deux manières :
Placez les données dans l'attribut de données d'une balise HTML, puis récupérez cet attribut dans votre code JavaScript.
@{
var donneesNonFiables = "<\"123\">";
}
<div id="donneesInjectees" donnees-donneesNonFiables="@donneesNonFiables" />
<script>
var donneesInjectees = document.getElementById("donneesInjectees");
var donneesNonFiablesCoteClient = donneesInjectees.getAttribute("donnees-donneesNonFiables");
document.write(donneesNonFiablesCoteClient);
</script>
Voici le code HTML qui sera généré :
<div id="donneesInjectees" donnees-donneesNonFiables="<"123">" />
<script>
var donneesInjectees = document.getElementById("donneesInjectees");
var donneesNonFiablesCoteClient = donneesInjectees.getAttribute("donnees-donneesNonFiables");
document.write(donneesNonFiablesCoteClient);
</script>
Ce code quant à lui donne le résultat suivant :
<"123">
2. Vous pouvez aussi appeler directement l'encodeur JavaScript :
@using System.Text.Encodings.Web;
@inject JavaScriptEncoder encodeur;
@{
var donneesNonFiables = "<\"123\">";
}
<script>
document.write("@encodeur.Encode(donneesNonFiables)");
</script>
Ce code est rendu comme suit dans le navigateur :
<script>
document.write("\u003C\u0022123\u0022\u003E");
</script>
Encodez les paramètres des URL
Pour créer une chaîne de requête d'URL à partir de données non fiables, encodez ces dernières avec UrlEncoder
.
var donneesNonFiables = "\"Donnees non fiables avec espaces et &\"";
var donneesFiables = _urlEncoder.Encode(donneesNonFiables);
Les données encodées de la variable donneesFiables seront :
%22Donnees%20non%20fiables%20avec%20espaces%20et%20%26%22
Les espaces, guillemets, signes de ponctuation et autres caractères non sécurisés sont convertis dans leur valeur hexadécimale et précédés d'un symbole de pourcentage (encodage-pourcent). Ainsi, un espace devient %20
.
Validez les saisies
La validation des saisies des utilisateurs est une méthode efficace pour bloquer les attaques XSS. Par exemple, une chaîne numérique contenant seulement les caractères 0 à 9 ne permettra pas de déclencher une attaque XSS. En revanche, la validation devient plus complexe lorsque vous acceptez du code HTML dans les saisies des utilisateurs, par exemple dans les commentaires de blog ou autres saisies du même type. L'analyse du HTML peut être difficile, voire impossible, étant donné qu'une telle attaque pourrait être cachée n'importe où. Le Markdown, combiné à un analyseur capable de supprimer le HTML intégré, est un moyen plus sûr d'accepter des saisies en texte riche. En règle générale, ne comptez pas uniquement sur la validation pour vous protéger des attaques XSS. Encodez toujours les saisies non fiables avant leur sortie, que les données aient été validées ou non.
En résumé
Voici les différents points que nous avons appris dans ce chapitre :
Une attaque par scripts intersites (XSS) consiste pour un pirate à injecter du code JavaScript (ou d'un autre langage de script) malveillant dans un site en le publiant avec d'autres données. Ce code malveillant fait alors partie du site et est exécuté chaque fois que le site est affiché dans le navigateur d'un utilisateur.
En tant que développeur, vous pouvez empêcher ce type d'attaque en :
encodant les données fournies par les utilisateurs avant leur affichage afin que le navigateur les interprète comme des données, mais pas comme du code ;
validant toutes les données saisies par les utilisateurs de sorte que celles que vous stockez ne contiennent aucune commande malveillante.
Parlons à présent des attaques par falsification de requête intersite (XSRF/CSRF). Il s'agit des attaques qui exploitent la relation de confiance entre un client et un serveur pour détourner des sessions utilisateur authentifiées.