Dans cette section, nous décrirons quelques principes généraux pour prévenir les vulnérabilités de script intersite et les façons d’utiliser diverses technologies courantes pour se protéger contre les attaques XSS.
La prévention des scripts inter-sites peut généralement être réalisée via deux couches de défense:
- Encoder les données sur la sortie
- Valider l’entrée à l’arrivée
Vous pouvez utiliser Burp Scanner pour analyser vos sites Web à la recherche de nombreuses vulnérabilités de sécurité, y compris XSS. La logique d’analyse de pointe de Burp reproduit les actions d’un attaquant qualifié et est capable d’obtenir une couverture élevée des vulnérabilités XSS. Vous pouvez utiliser Burp Scanner pour avoir l’assurance que vos défenses contre les attaques XSS fonctionnent efficacement.
Encoder les données en sortie
L’encodage doit être appliqué directement avant que les données contrôlables par l’utilisateur ne soient écrites sur une page, car le contexte dans lequel vous écrivez détermine le type d’encodage que vous devez utiliser. Par exemple, les valeurs à l’intérieur d’une chaîne JavaScript nécessitent un type d’échappement différent de ceux d’un contexte HTML.
Dans un contexte HTML, vous devez convertir des valeurs non sur liste blanche en entités HTML:
-
<
convertit en:<
-
>
convertit en:>
Dans un contexte de chaîne JavaScript, les valeurs non alphanumériques doivent être échappées Unicode:
-
<
convertit en:\u003c
-
>
convertit en:\u003e
Parfois, vous devrez appliquer plusieurs couches d’encodage, dans le bon ordre. Par exemple, pour intégrer en toute sécurité une entrée utilisateur dans un gestionnaire d’événements, vous devez gérer à la fois le contexte JavaScript et le contexte HTML. Vous devez donc d’abord échapper Unicode à l’entrée, puis l’encoder en HTML:
<a href="#" onclick="x='This string needs two layers of escaping'">test</a>
Valider l’entrée à l’arrivée
L’encodage est probablement la ligne de défense XSS la plus importante, mais il n’est pas suffisant pour empêcher les vulnérabilités XSS dans tous les contextes. Vous devez également valider l’entrée aussi strictement que possible au moment où elle est reçue pour la première fois d’un utilisateur.
Exemples de validation d’entrée ::
- Si un utilisateur soumet une URL qui sera renvoyée dans les réponses, validant qu’elle commence par un protocole sûr tel que HTTP et HTTPS. Sinon, quelqu’un pourrait exploiter votre site avec un protocole nuisible comme
javascript
oudata
. - Si un utilisateur fournit une valeur qu’il s’attendait à être numérique, validant que la valeur contient réellement un entier.
- Valider cette entrée ne contient qu’un ensemble de caractères attendu.
La validation des entrées devrait idéalement fonctionner en bloquant les entrées invalides. Une autre approche, consistant à essayer de nettoyer les entrées invalides pour les rendre valides, est plus sujette aux erreurs et devrait être évitée dans la mesure du possible.
Liste blanche vs liste noire
La validation des entrées devrait généralement utiliser des listes blanches plutôt que des listes noires. Par exemple, au lieu d’essayer de faire une liste de tous les protocoles nuisibles (javascript
, data
, etc.), il suffit de faire une liste de protocoles sécurisés (HTTP, HTTPS) et d’interdire tout ce qui ne figure pas dans la liste. Cela garantira que votre défense ne se brise pas lorsque de nouveaux protocoles nuisibles apparaissent et la rendra moins vulnérable aux attaques qui cherchent à obscurcir des valeurs invalides pour échapper à une liste noire.
Autoriser le HTML « sûr »
Permettre aux utilisateurs de publier des balises HTML doit être évité dans la mesure du possible, mais c’est parfois une exigence métier. Par exemple, un site de blog peut autoriser la publication de commentaires contenant un balisage HTML limité.
L’approche classique consiste à essayer de filtrer les balises et JavaScript potentiellement nuisibles. Vous pouvez essayer de l’implémenter à l’aide d’une liste blanche de balises et d’attributs sécurisés, mais en raison des divergences dans les moteurs d’analyse du navigateur et des bizarreries telles que mutation XSS, cette approche est extrêmement difficile à implémenter en toute sécurité.
La moins mauvaise option est d’utiliser une bibliothèque JavaScript qui effectue le filtrage et l’encodage dans le navigateur de l’utilisateur, telle que DOMPurify. D’autres bibliothèques permettent aux utilisateurs de fournir du contenu au format markdown et de convertir le markdown en HTML. Malheureusement, toutes ces bibliothèques ont de temps en temps des vulnérabilités XSS, ce n’est donc pas une solution parfaite. Si vous en utilisez un, vous devez surveiller de près les mises à jour de sécurité.
Note
En plus du JavaScript, d’autres contenus tels que le CSS et même le HTML régulier peuvent être nuisibles dans certaines situations.
Attaques utilisant des CSS malveillants
Comment empêcher les XSS à l’aide d’un moteur de modèles
De nombreux sites Web modernes utilisent des moteurs de modèles côté serveur tels que Twig et Freemarker pour intégrer du contenu dynamique en HTML. Ceux-ci définissent généralement leur propre système d’échappement. Par exemple, dans Twig, vous pouvez utiliser le filtre e()
, avec un argument définissant le contexte:
{{ user.firstname | e('html') }}
Certains autres moteurs de modèles, tels que Jinja et React, échappent au contenu dynamique par défaut, ce qui empêche efficacement la plupart des occurrences de XSS.
Nous vous recommandons d’examiner attentivement les fonctionnalités d’échappement lorsque vous évaluez s’il faut utiliser un moteur de modèle ou un framework donné.
Note
Si vous concaténez directement les entrées utilisateur dans des chaînes de modèles, vous serez vulnérable à l’injection de modèles côté serveur, qui est souvent plus grave que XSS.
Comment empêcher XSS en PHP
En PHP, il existe une fonction intégrée pour encoder des entités appelées htmlentities
. Vous devez appeler cette fonction pour échapper votre entrée dans un contexte HTML. La fonction doit être appelée avec trois arguments:
- Votre chaîne d’entrée.
-
ENT_QUOTES
, qui est un indicateur qui spécifie que toutes les guillemets doivent être codés. - Le jeu de caractères, qui dans la plupart des cas devrait être UTF-8.
Par exemple:
<?php echo htmlentities($input, ENT_QUOTES, 'UTF-8');?>
Dans un contexte de chaîne JavaScript, vous devez extraire l’entrée Unicode-escape comme déjà décrit. Malheureusement, PHP ne fournit pas d’API pour échapper à Unicode une chaîne. Voici du code pour le faire en PHP:
<?php
function jsEscape($str) {
$output = '';
$str = str_split($str);
for($i=0;$i<count($str);$i++) {
$chrNum = ord($str);
$chr = $str;
if($chrNum === 226) {
if(isset($str) && ord($str) === 128) {
if(isset($str) && ord($str) === 168) {
$output .= '\u2028';
$i += 2;
continue;
}
if(isset($str) && ord($str) === 169) {
$output .= '\u2029';
$i += 2;
continue;
}
}
}
switch($chr) {
case "'":
case '"':
case "\n";
case "\r";
case "&";
case "\";
case "<":
case ">":
$output .= sprintf("\u%04x", $chrNum);
break;
default:
$output .= $str;
break;
}
}
return $output;
}
?>
Voici comment utiliser la fonction jsEscape
en PHP:
<script>x = '<?php echo jsEscape($_GET)?>';</script>
Vous pouvez également utiliser un moteur de modèle.
Comment empêcher XSS côté client en JavaScript
Pour échapper aux entrées utilisateur dans un contexte HTML en JavaScript, vous avez besoin de votre propre encodeur HTML car JavaScript ne fournit pas d’API pour encoder HTML. Voici un exemple de code JavaScript qui convertit une chaîne en entités HTML:
function htmlEncode(str){
return String(str).replace(//gi, function(c){
return '&#'+c.charCodeAt(0)+';';
});
}
Vous utiliseriez alors cette fonction comme suit:
<script>document.body.innerHTML = htmlEncode(untrustedValue)</script>
Si votre entrée se trouve dans une chaîne JavaScript, vous avez besoin d’un encodeur qui exécute l’échappement Unicode. Voici un exemple d’encodeur Unicode:
function jsEscape(str){
return String(str).replace(//gi, function(c){
return '\u'+('0000'+c.charCodeAt(0).toString(16)).slice(-4);
});
}
Vous utiliseriez alors cette fonction comme suit:
<script>document.write('<script>x="'+jsEscape(untrustedValue)+'";<\/script>')</script>
Comment empêcher XSS dans jQuery
La forme la plus courante de XSS dans jQuery est lorsque vous passez une entrée utilisateur à un sélecteur jQuery. Les développeurs Web utiliseraient souvent location.hash
et le transmettraient au sélecteur qui provoquerait XSS car jQuery rendrait le code HTML. jQuery a reconnu ce problème et a corrigé sa logique de sélection pour vérifier si l’entrée commence par un hachage. Maintenant, jQuery ne rendra HTML que si le premier caractère est <
. Si vous transmettez des données non fiables au sélecteur jQuery, assurez-vous d’échapper correctement la valeur à l’aide de la fonction jsEscape
ci-dessus.
Atténuer le XSS à l’aide de la stratégie de sécurité du contenu (CSP)
La politique de sécurité du contenu (CSP) est la dernière ligne de défense contre les scripts intersite. Si votre prévention XSS échoue, vous pouvez utiliser CSP pour atténuer XSS en limitant ce qu’un attaquant peut faire.
CSP vous permet de contrôler diverses choses, par exemple si des scripts externes peuvent être chargés et si des scripts en ligne seront exécutés. Pour déployer CSP, vous devez inclure un en-tête de réponse HTTP appelé Content-Security-Policy
avec une valeur contenant votre stratégie.
Un exemple de CSP est le suivant:
default-src 'self'; script-src 'self'; object-src 'none'; frame-src 'none'; base-uri 'none';
Cette stratégie spécifie que les ressources telles que les images et les scripts ne peuvent être chargées qu’à partir de la même origine que la page principale. Ainsi, même si un attaquant peut injecter avec succès une charge utile XSS, il ne peut charger que des ressources à partir de l’origine actuelle. Cela réduit considérablement les chances qu’un attaquant puisse exploiter la vulnérabilité XSS.
Si vous avez besoin de charger des ressources externes, assurez-vous d’autoriser uniquement les scripts qui n’aident pas un attaquant à exploiter votre site. Par exemple, si vous mettez certains domaines sur liste blanche, un attaquant peut charger n’importe quel script de ces domaines. Dans la mesure du possible, essayez d’héberger des ressources sur votre propre domaine.
Si cela n’est pas possible, vous pouvez utiliser une stratégie basée sur le hachage ou nonce pour autoriser les scripts sur différents domaines. Un nonce est une chaîne aléatoire ajoutée en tant qu’attribut d’un script ou d’une ressource, qui ne sera exécutée que si la chaîne aléatoire correspond à celle générée par le serveur. Un attaquant est incapable de deviner la chaîne randomisée et ne peut donc pas appeler un script ou une ressource avec un nonce valide et la ressource ne sera donc pas exécutée.