Articles

CORS, XSS et CSRF avec des exemples en 10 minutes

Cet article devrait être votre point d’entrée pour les normes de sécurité web existantes, les attaques web les plus courantes et les méthodes pour les prévenir. A la fin, vous découvrirez également comment et pourquoi Samy était le héros de tout le monde.(sauf celui de Rupert Murdoch, je suppose)

Le cross-origin resource sharing, ou CORS, est une fonctionnalité de sécurité d’IE10+, Chrome 4+, Firefox 3.5+ ou presque toutes les versions de navigateurs sorties après 2012, à l’exception d’Opera Mini.

Lorsque CORS est configuré sur un serveur qui est disponible sur le domaine website.com alors les ressources de ce domaine qui sont demandées par AJAX doivent être initiées à partir d’actifs qui sont servis à partir de ce même domaine.
CORS

En d’autres termes, si nous activons CORS sur domain-b.com et le configurons pour permettre seulement GET requêtes du domaine domain-b.com alors si vous voulez utiliser l’image disponible sous https://domain-b.com/images/example.png dans le canevas sur votre site web qui est hébergé sur domain-a.com, que cette image ne sera pas chargée pour la plupart de vos visiteurs.
Vos ressources protégées par CORS seront toujours disponibles lorsqu’elles seront demandées par n’importe quel outil ou navigateur qui ne respecte pas CORS policy.

Configuration CORS

Les CORS sont désactivés par défaut, ce qui signifie qu’il n’y a pas de gestionnaire de serveur adéquat qui configurera CORS, ce qui signifie que vous ne pouvez pas accéder à des ressources d’origine différente dans votre XHR. Fondamentalement, si vous ne faites rien ou si vous activez spécifiquement CORS seulement pour des domaines spécifiques, alors toute demande AJAX essayant d’accéder à vos ressources sera rejetée parce que les navigateurs web sont respectueux de la CORS policy.
C’est la raison pour laquelle vous rencontrez le problème CORS lorsque vous commencez à développer des SPA en utilisant VueJS et NodeJS. Votre application VueJS est hébergée sur http://localhost:8080 et lorsque vous essayez d’accéder à l’application serveur NodeJS sur http://localhost:8000, vous obtenez « No Access-Control-Allow-Origin header is present » parce que ce sont deux ORIGINS différentes (combinaison de PROTOCOL, HOST et PORT).

Cool fix pour le problème CORS en mode développement VueJS est de définir le proxy devServer dans votre fichier vue.config.js comme suit :

module.exports = { ... devServer: { proxy: 'http://localhost:8000', }, ...}
Entrer dans le mode plein écran Sortir du mode plein écran

Pour configurer CORS en production, vous devez ajouter un auditeur approprié pour la requête OPTIONS. Ce listener devrait envoyer la réponse 200 avec no body mais avec Headers qui définira votre politique CORS souhaitée :

Access-Control-Allow-Origin: https://domain-b.comAccess-Control-Allow-Methods: GET
Entrer en mode plein écran Sortir en mode plein écran

Pour plus d’infos sur la façon de configurer CORS, vérifiez https://enable-cors.org/index.html, et pour plonger plus profondément dans CORS policyvérifiez https://livebook.manning.com/book/cors-in-action/part-1/

XSS

XSS signifie Cross Site Scripting et c’est un type d’attaque par injection. Elle est classée 7e sur les 10 principales vulnérabilités identifiées par l’OWASP en 2017. Le Cross site scripting est la méthode où l’attaquant injecte un script malveillant dans un site web de confiance.(section mise à jour, merci Sandor) Il existe 3 types de ces attaques.

  1. Stored XSS – Vulnérabilité provenant des entrées utilisateur non protégées et non sanitizées qui sont directement stockées dans la base de données et affichées aux autres utilisateurs
  2. Reflected XSS – Vulnérabilité provenant des valeurs non protégées et non sanitizées des URL qui sont directement utilisées dans les pages web
  3. DOM based XSS – Similaire à reflected XSS, des valeurs non protégées et non sanitisées provenant d’URL utilisées directement dans les pages web, à la différence que le DOM based XSS ne va même pas du côté du serveur

Attaque

1. Stored XSS

Voici un exemple d’attaque. L’attaquant arrive sur votre site web et trouve un champ de saisie non protégé comme le champ de commentaire ou le champ de nom d’utilisateur et entre un script malveillant à la place de la valeur attendue. Après cela, chaque fois que cette valeur sera affichée aux autres utilisateurs, elle exécutera un code malveillant. Le script malveillant peut essayer d’accéder à votre compte sur d’autres sites Web, peut être impliqué dans une attaque DDoS ou autre. Représentation visuelle(source geeksforgeeks.org):

XSS example

2. XSS réfléchi

Le XSS réfléchi est une attaque qui se produit lorsque l’attaquant découvre une page avec une telle vulnérabilité, par exemple:

l’URL attendue : https://mywebpage.com/search?q=javascript
URL malveillante : https://mywebpage.com/search?q=<script>alert('fortunately, this will not work!')</script>

<body>...<div> showing results for keyword <script> document.write(window.location.href.substr(window.location.href.indexOf('q=') + 2))</script></div>......JavaScript results......</body>
Entrer en mode plein écran Sortir en mode plein écran

Après la découverte, l’attaquant appâte l’utilisateur pour qu’il clique sur une telle URL malveillante et voilà. Les données sensibles de l’utilisateur sont exploitées.

Cycle de vie de l’attaque ilustré dans l’exemple fourni par geekforgeeks.com:

Reflected XSS example

3. DOM based XSS

Ce type d’attaque est le même que celui reflété mais avec la différence que la partie malveillante URL ne sera pas du tout envoyée au serveur. Pour l’exemple ci-dessus :

l’URL attendue : https://mywebpage.com/search?q=javascript
URL malveillante (XSS réfléchi) : https://mywebpage.com/search?q=<script>alert('fortunately, this will not work!')</script>
URL malveillante(DOM based XSS) : https://mywebpage.com/search#q=<script>alert('fortunately, this will not work!')</script>

La différence réside dans l’utilisation du caractère # au lieu de ?. Les navigateurs n’envoient pas la partie de l’URL après # au serveur donc ils la passent directement à votre code client.

Protection

Chaque valeur qui peut être saisie par l’utilisateur et qui est utilisée dans votre application(soit du côté serveur soit du côté client) doit être traitée comme une donnée non fiable et doit donc être traitée avant d’être utilisée ! Vous devriez faire un contrôle de sécurité dans votre application serveur et votre application client, aussi bien!
Comme montré dans la documentation VueJS par lui-même échappe la chaîne avant d’obtenir la valeur de la variable. Les versions plus récentes d’Angular échappent également les chaînes implicitement, donc si vous utilisez Vanilla JS, JQuery ou similaire, vous devriez implémenter l’échappement des chaînes manuellement.

Il y a trois approches les plus courantes sur le traitement des données non fiables sont listées ci-dessous et la méthode idéale dépend du type réel du champ que vous devez traiter.

1. Validation des chaînes

La validation est la méthode où l’utilisateur définit un ensemble de règles, et demande aux données non fiables de satisfaire ces règles avant de continuer. Cette méthode est bonne pour les valeurs numériques, le nom d’utilisateur, l’email, le mot de passe et les champs similaires avec un ensemble concret de règles syntaxiques.

Vérifiez les bibliothèques existantes pour votre framework avant d’envisager d’écrire des validateurs par vous-même.

2. String escape

La méthode escape est utile pour les cas où vous devez permettre à l’utilisateur d’utiliser des signes de ponctuation. Cette méthode parcourt la chaîne et recherche les caractères spéciaux, tels que < > et les remplace par le nom d’entité de caractères HTML approprié. Voici la fonction de base que vous pourriez utiliser:

function escapeText(text) { return text.replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') .replace(/"/g, '&quot;')}
Entrer dans le mode plein écran Sortir du mode plein écran

Encore, vérifiez les bibliothèques existantes avant d’écrire la vôtre.

3. Nettoyage de la chaîne

Le nettoyage de la chaîne est utilisé lorsque l’utilisateur est autorisé à entrer certaines balises HTML dans ses commentaires, articles ou similaires. La méthode sanitize parcourt le texte et recherche les balises HTML que vous spécifiez et les supprime. L’une des bibliothèques les plus populaires qui utilise cette approche est Google Closure.
Cette méthode est coûteuse en ressources et considérée comme nuisible, donc faites plus de recherches avant de la choisir.

Les navigateurs Web(pas de sources disponibles depuis quelle version, IE a corrigé ce problème en 2014.) échappent automatiquement les URL avant de les envoyer au côté serveur et les rendent disponibles dans l’objet window.location également, donc les 2e et 3e types d’attaque sont ici juste pour être conscient d’eux et pour préciser que les paramètres d’URL doivent également être traités comme des données non fiables.

Pour des infos plus détaillées sur XSS et comment protéger correctement votre application si vous faites tourner beaucoup de données non fiables, veuillez consulter la cheatsheet de l’OWASP sur la prévention XSS.

CSRF

La contrefaçon de requête intersite ou CSRF est un type d’attaque qui se produit lorsqu’un site web, un courriel, un blog, un message instantané ou un programme malveillant amène le navigateur web d’un utilisateur à effectuer une action non désirée sur un autre site de confiance où l’utilisateur est authentifié. Cette vulnérabilité est possible lorsque le navigateur envoie automatiquement une ressource d’autorisation, telle que le cookie de session, l’adresse IP ou similaire avec chaque requête.

ATTENTAT

Supposons que l’utilisateur est connecté à votre application web boursière non protégée et que vous utilisez soit le cookie de session soit le cookie JWT pour l’authentification. L’attaquant utilise également votre service et est en mesure de vérifier le fonctionnement de votre API. L’attaquant incite l’utilisateur à exécuter un script (en cliquant sur un lien SPAM dans un e-mail ou autre) qui enverra une requête à votre API https://www.stockexchange.com/users/withdraw?how_much=all&address=MY_ADDRESS (conception terrible de l’API, ne demandez pas). Puisque la requête est exécutée à partir d’un navigateur qui envoie des données utiles d’authentification avec chaque requête, votre serveur web boursier authentifiera l’utilisateur avec succès et exécutera la transaction et l’utilisateur trompé perdra tout son solde sans même s’en rendre compte car tout s’est passé en arrière-plan. Représentation visuelle(source miro.medium.com)
CSRF attack

PROTECTION

Heureusement, il existe des schémas faciles à mettre en œuvre qui empêchent ces attaques web. L’un des modèles les plus courants est l’utilisation de CSRF token. La procédure de base est la suivante :

  1. Générer un jeton unique pour chaque demande de l’utilisateur, appelé CSRF token.
  2. Le stocker en toute sécurité sur le serveur et le renvoyer à l’utilisateur comme charge utile de la réponse.
  3. Stocker CSRF token côté client.
  4. Lorsque l’utilisateur essaie d’exécuter une requête changeant d’état*, envoyez cette CSRF token avec la requête comme charge utile.
  5. Avant d’exécuter cette requête côté serveur, vérifiez si CSRF token est présente et si elle est valide.

C’est la façon la plus simple d’empêcher l’attaque CSRF pour tous les utilisateurs.

Si vous n’avez affaire qu’à des visiteurs qui utilisent des navigateurs modernes, alors vous pouvez compter sur l’attribut SameSite du cookie de session.(merci Gergely)

Puisque les réponses du serveur sont traitables dans la réponse XHR, alors il n’y a aucune protection sur l’attaque CSRF si votre application web est vulnérable XSS !

Pour plonger plus profondément, consultez la cheatsheet de l’OWASP sur CSRF.

BONUS

Court documentaire sur Samy, auteur du ver qui a fait tomber MySpace en 2005 en abusant de la vulnérabilité XSS, passant la défense CSRF de MySpace.
https://youtu.be/DtnuaHl378M

Plus d’infos sur le ver de Samy
https://samy.pl/myspace/tech.html

.