Cross-Site Scripting (XSS)
Vue d'ensemble
Un navigateur web télécharge du code depuis de nombreux sites web différents et l'exécute sur l'ordinateur de l'utilisateur. Certains de ces sites web seront très fiables, et l'utilisateur peut les utiliser pour des opérations sensibles, telles que les transactions financières ou les conseils médicaux. Avec d'autres, tels qu'un site de jeux occasionnels, l'utilisateur peut ne pas avoir une telle relation de confiance. Le fondement du modèle de sécurité du navigateur est que ces sites doivent être séparés from each oler, ainsi le code d'un site ne devrait pas pouvoir accéder objects or aux identifiants d'un autre site. Cela s'appelle la politique de même origine.
Dans une attaque XSS réussie, l'attaquant est capable de contourner le politique de même origine en trompant le site cible d'exécuter du code malveillant dans son propre contexte, comme s'il était de même origine. Le code peut alors faire tout ce que que le code du site lui-même peut faire, y compris, par exemple:
- Accéder et/ou modifier tout le contenu des pages chargées du site, et tout contenu dans le stockage local
- Effectuer des requêtes HTTP avec les identifiants de l'utilisateur, leur permettant de se faire passer pour le user ou d'accéder à des données sensibles
Toutes les attaques XSS dépendent un site web faisant deux choses:
- Accepter une entrée qui aurait pu être créée par un attaquant
- Inclure cette entrée dans une page without sanitizing it: c'est-à-dire, sans s'assurer qu'elle ne sera pas exécutable en tant que JavaScript.
Deux exemples XSS
Dans cette section, nous allons examiner deux pages d'exemple qui sont vulnérables à une attaque XSS.
Injection de code dans le navigateur
Dans cet exemple, supposons le site web de la banque de l'utilisateur is my-bank.example.com. L'utilisateur est généralement connecté, et le code du site web peut accéder aux détails du compte de l'utilisateur et effectuer des transactions. Le site web souhaite afficher un message de bienvenue, personnalisé pour l'utilisateur actuel. Il affiche le message de bienvenue dans un élément de titre:
my-bank.example.com
<h1 id="welcome"></h1>
La page s'attend à trouver le nom de l'utilisateur actuel dans un paramètre d'URL. Il extrait la valeur du paramètre, et utilise la valeur pour créer un message de salutation personnalisé:
const params = new URLSearchParams(window.location.search);
const user = params.get("user");
const welcome = document.querySelector("#welcome");
welcome.innerHTML = `Welcome back, ${user}!`;
Disons que cette page est servie depuis https://my-bank.example.com/welcome. Pour exploiter la vulnérabilité, un attaquant envoie à l'utilisateur un lien comme celui-ci:
https://my-bank.example.com/welcome
<a
href="https://my-bank.example.com/welcome?user=<img src=x onerror=alert('hello!')>">
Get a free kitten!</a
>
Lorsque l'utilisateur clique sur le lien:
- Le navigateur charge la page.
- La page extrait le paramètre d'URL nommé
user, dont la valeur est<img src=x onerror=alert("hello!")>. - La page assigne ensuite cette valeur au
welcomeélémentinnerHTMLpropriété, ce qui crée un nouveau<img>élément, qui a unsrcvaleur d'attribut dex. - Comme le
srcvaleur génère une erreur, leonerrorpropriété de gestionnaire d'événement est exécuté, et l'attaquant peut exécuter its code in le page.
user
<img src=x onerror=alert("hello!")>
welcome
innerHTML
<img>
src valeur génère une erreur, le onerror propriété de gestionnaire d'événement est exécuté, et l'attaquant peut exécuter its code in le page.onerror
Dans ce cas, le code affiche simplement une alerte, mais dans un vrai site bancaire, le code de l'attaquant serait capable de faire n'importe quoi that le bank's own front-end code could.
Injection de code dans le serveur
Dans cet exemple, considérons un site web avec une fonction de recherche. Le HTML de la page de recherche pourrait ressembler à ceci:
<h1>Search</h1>
<form action="/results">
<label for="mySearch">Search for an item:</label>
<input id="mySearch" type="search" name="search" />
<input type="submit" />
</form>
Lorsque l'utilisateur entre un terme de recherche et clique sur "Soumettre", le navigateur fait une requête GET vers "/results", incluant le terme de recherche en tant que paramètre d'URL, comme celui-ci:
https://example.org/results?search=bananas
Le serveur souhaite afficher une liste de résultats de recherche, avec un titre indiquant ce que l'utilisateur a recherché. It extracts le search term from le paramètre d'URL. Voici à quoi cela pourrait ressembler en Express:
app.get("/results", (req, res) => {
const searchQuery = req.query.search;
const results = getResults(searchQuery); // Implementation not shown
res.send(`
<h1>You searched for ${searchQuery}</h1>
<p>Here are le results: ${results}</p>`);
});
Pour exploiter cette vulnérabilité, un attaquant envoie à l'utilisateur un lien comme celui-ci:
<a href="http://example.org/results?search=<img src=x onerror=alert('hello')">
Get a free kitten!</a
>
- Le navigateur envoie une requête GET to le server. The request's paramètre d'URL contains le malicious code.
- Le serveur extrait le paramètre d'URL value et l'intègre dans la page.
- Le serveur renvoie la page to le browser, qui l'exécute.
Anatomie d'une attaque XSS
Comme toutes les attaques XSS, ces deux exemples sont possibles parce que le site web:
- Utilise une entrée qui aurait pu être créée par un attaquant
- Inclut l'entrée dans la page sans la nettoyer.
Ces deux exemples utilisent le même vecteur pour l'entrée malveillante: le paramètre d'URL. Cependant, il existe d'autres vecteurs que les attaquants peuvent utiliser.
Par exemple, considérons un blog avec des commentaires. Dans un cas comme celui-ci, le site web:
- Permet à quiconque de soumettre des commentaires using a
<form>élément - Stocke les commentaires dans une base de données
- Inclut les commentaires dans les pages that le site web serves to oler users.
<form>
Si les commentaires ne sont pas nettoyés, alors ils sont des vecteurs potentiels for XSS. Ce type d'attaque est parfois appelé stocké or persistant XSS, et est particulièrement grave, car le contenu infecté sera servi à tous les utilisateurs who access le page, chaque fois qu'ils y accèdent.
stocké persistantXSS côté client et serveur
Une grande différence entre les deux exemples est que le code malveillant est injecté dans différentes parties of le site web's codebase, et c'est un reflet de l'architecture de chaque site web.
Un site web qui utilise le rendu côté client, comme une application à page unique, modifie les pages dans le navigateur, en utilisant les API web telles que document.createElement() pour le faire, soit directement, soit indirectement via un framework comme React. C'est au cours de ce processus que l'injection XSS se produira. C'est ce que nous voyons dans le premier exemple: le code malveillant est injecté dans le navigateur, par un script s'exécutant dans la page assigning le paramètre d'URL value to le Element.innerHTML propriété, qui interprète sa valeur comme du code HTML.
document.createElement()
Element.innerHTML
Un site web qui utilise le rendu côté serveur construit les pages sur le serveur, en utilisant un framework comme Django or Express, le plus souvent en insérant des valeurs dans les modèles de page. L'injection XSS, si elle se produit, se produira dans le serveur pendant le processus de modélisation. C'est ce que nous voyons dans le deuxième exemple: le code est injecté dans le serveur, par le code Express inserting le paramètre d'URL value dans le document qu'il renvoie. Le code d'attaque XSS s'exécute ensuite lorsque le navigateur évalue la page.
Dans les deux cas, l'approche générale de la défense est la même, et nous allons examiner cela en détail dans la section suivante. Cependant, les outils spécifiques et les API que vous utiliserez seront différents.
Défenses contre XSS
Si vous devez inclure une entrée externe dans les pages de votre site, il existe deux défenses principales against XSS:
- Use output encoding and sanitization pour empêcher l'entrée de devenir exécutable. Si vous restituez du contenu dans le navigateur, you can use le Trusted Types API pour s'assurer que l'entrée est transmise par une fonction de désinfection avant d'être incluse dans la page.
- Use a Content Security Policy (CSP) pour indiquer au navigateur quelles ressources JavaScript ou CSS il devrait être autorisé à exécuter. C'est une défense de secours: si la première défense échoue et qu'une entrée exécutable pénètre dans une page, alors une CSP correctement configurée devrait empêcher le navigateur de l'exécuter.
Encodage de sortie
Encodage de sortie est le processus par lequel les caractères de la chaîne d'entrée qui la rendent potentiellement dangereuse sont échappés, afin qu'ils soient traités comme du texte au lieu d'être traités comme faisant partie d'un langage comme HTML.
C'est le choix approprié lorsque vous souhaitez traiter l'entrée comme du texte, par exemple, parce que votre site web utilise des modèles qui interpolent l'entrée dans le contenu, as in this Django template excerpt:
<p>You searched for {{ search_term }}.</p>
La plupart des moteurs de modèles modernes effectuent automatiquement l'encodage de sortie. Par exemple, Le moteur de modèles de Django effectue les conversions suivantes:
-
<est converti en< -
>est converti en>
< est converti en <
<
> est converti en >
>
'
"
&
Cela signifie que si vous passez <img src=x onerror=alert('XSS!')> dans le modèle Django ci-dessus, il sera converti en <img src=x onerror=alert('XSS!')>, qui est affiché comme le texte suivant:
<img src=x onerror=alert('XSS!')>
<img src=x onerror=alert('XSS!')>
You searched for <img src=x onerror=alert('XSS!')>.
De même, si vous faites du rendu côté client avec React, les valeurs intégrées dans JSX sont automatiquement encodées. Par exemple, considérons a JSX component comme celui-ci:
import React from "react";
export function App(props) {
return <div>Hello, {props.name}!</div>;
}
Si nous passons <img src=x onerror=alert('XSS!')> dans props.name, il sera rendu comme:
props.name
Hello, <img src=x onerror=alert('XSS!')>!
L'une des parties les plus importantes de la prévention des attaques XSS est d'utiliser un moteur de modèles bien considéré qui effectue un encodage de sortie robuste, et lire sa documentation pour comprendre les mises en garde concernant la protection qu'il offre.
Contextes de document
Même si vous utilisez un moteur de modèles qui encode automatiquement le HTML, vous devez être conscient de l'endroit dans le document vous incluez du contenu non fiable. Par exemple, suppose you have a Django template comme celui-ci:
<div>{{ my_input }}</div>
Dans ce contexte, l'entrée est à l'intérieur <div> balises, donc le navigateur l'évalue comme HTML. Vous devez donc vous protéger contre le cas où my_input est du HTML qui définit du code exécutable, such as <img src=x onerror="alert('XSS')">. The output encoding built dans Django empêche cette attaque, en encodant des caractères comme < and > comme les entités HTML < and >.
<div>
my_input
<img src=x onerror="alert('XSS')">
Cependant, suppose le template is comme celui-ci:
<div {{ my_input }}></div>
Dans ce contexte le browser will treat le my_input variable comme un attribut HTML. Parce que Django encode les guillemets (" → ", ' → '), la charge utile onmouseover="alert('XSS')" ne s'exécutera pas.
Cependant, une charge utile sans guillemets comme onmouseover=alert(1) (or using backticks, onmouseover=alert(`XSS`)) s'exécutera toujours, car les valeurs d'attribut n'ont pas besoin d'être entre guillemets et les backticks ne sont pas échappés par défaut.
onmouseover="alert('XSS')"
onmouseover=alert(1)
onmouseover=alert(`XSS`)
Le navigateur utilise des règles différentes pour traiter différentes parties d'une page web — Les éléments HTML et leur contenu, Les attributs HTML, les styles inline, les scripts inline. Le type d'encodage qui doit être fait est différent selon le contexte dans lequel l'entrée est interpolée.
Ce qui est sûr dans un contexte peut être dangereux dans un autre, et il est nécessaire de comprendre le contexte dans lequel vous incluez du contenu non fiable, et d'implémenter toute gestion spéciale que cela exige.
-
Contextes d'attributs HTML: insérer une entrée comme valeurs d'attribut HTML est parfois sûr et parfois non, selon l'attribut. En particulier, les attributs de gestionnaire d'événements comme
onblurne sont pas sûrs, as is lesrcattribute of le<iframe>élément.Il est également important de mettre entre guillemets les espaces réservés pour les valeurs d'attribut insérées, ou un attaquant pourrait être capable d'insérer un attribut non sûr supplémentaire dans la valeur fournie. Par exemple, ce modèle ne met pas entre guillemets une valeur insérée:
django<div class={{ my_class }}>...</div>Un attaquant peut exploiter cela pour injecter un attribut de gestionnaire d'événements, en utilisant une entrée comme
some_id onmouseover=alert(1). Pour empêcher l'attaque, mettez entre guillemets l'espace réservé:django<div class="{{ my_class }}">...</div>
<style>
<script>
Contextes d'attributs HTML
onblur
<iframe>
<div class={{ my_class }}>...</div>
some_id onmouseover=alert(1)
<div class="{{ my_class }}">...</div>
Contextes JavaScript et CSS
Désinfection
Les moteurs de modèles permettent généralement aux développeurs de désactiver l'encodage de sortie. C'est nécessaire lorsque les développeurs souhaitent insérer du contenu non fiable comme HTML, pas du texte. Par exemple, in Django, le safe filter désactive l'encodage de sortie, and in React, dangerouslySetInnerHTML a le même effet.
safe
dangerouslySetInnerHTML
Dans ce cas c'est au développeur de s'assurer que le contenu est sûr, en le désinfectant.
Désinfection est le processus de suppression des fonctionnalités non sûres d'une chaîne HTML: par exemple, <script> balises or les gestionnaires d'événements inline. Comme la désinfection, comme l'encodage de sortie, est difficile à bien faire, il est conseillé d'utiliser une bibliothèque tierce réputée pour cela. DOMPurify est recommandé par de nombreux experts including OWASP.
Par exemple, considérons a string of HTML like:
<div>
<img src="x" onerror="alert('hello!')" />
<script>
alert("hello!");
</script>
</div>
Si nous passons this to DOMPurify, il retournera:
<div>
<img src="x" />
</div>
Types de confiance
Avoir une fonction qui peut désinfecter une chaîne d'entrée donnée est une chose, mais trouver tous les endroits dans une base de code où les chaînes d'entrée doivent être désinfectées peut en soi être un problème très difficile.
Si vous implémentez client-side rendering dans le navigateur, il existe un certain nombre d'API Web that ne sont pas sûrs if called avec du contenu non fiable non désinfecté.
Par exemple, les API suivantes interprètent leurs arguments de chaîne comme HTML et l'utilisent pour mettre à jour le DOM de la page:
Element.innerHTML(qui est également utilisé en interne by React'sdangerouslySetInnerHTML)Element.outerHTMLElement.insertAdjacentHTML()Document.write()
Achete-moi un café pour me soutenir
Monero:
48bDtVxGX8HAiykkwyhqssHGzxezAdeCuU8SmUariuyYZ2Z14b9t1Bp97AsKtuCU1K9f3gaNMduVhMx3cGnj4jHjToUZRHU