Limiter les scripts intersites (XSS) avec une CSP (Content Security Policy) stricte.

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

Le script intersites (XSS), qui permet d'injecter des scripts malveillants dans une application Web, est l'une des plus grandes failles de sécurité Web depuis plus de dix ans.

La Content Security Policy (CSP) est une couche de sécurité supplémentaire qui permet de limiter les attaques XSS. Pour configurer une CSP, ajoutez l'en-tête HTTP Content-Security-Policy à une page Web et définissez des valeurs qui contrôlent les ressources que l'user-agent peut charger pour cette page.

Cette page explique comment utiliser une CSP basée sur des nonces ou des hachages pour limiter les attaques XSS, au lieu des CSP basées sur une liste d'autorisation d'hôtes couramment utilisées, qui exposent souvent la page aux attaques XSS, car elles peuvent être contournées dans la plupart des configurations.

Terme clé : un nonce est un nombre aléatoire utilisé une seule fois que vous pouvez utiliser pour marquer une balise <script> comme fiable.

Terme clé : une fonction de hachage est une fonction mathématique qui convertit une valeur d'entrée en une valeur numérique compressée appelée hachage. Vous pouvez utiliser un hachage (par exemple, SHA-256) pour marquer une balise <script> intégrée comme fiable.

Une Content Security Policy basée sur des nonces ou des hachages est souvent appelée CSP stricte. Lorsqu'une application utilise une CSP stricte, les pirates informatiques qui trouvent des failles d'injection HTML ne peuvent généralement pas les utiliser pour forcer le navigateur à exécuter des scripts malveillants dans un document vulnérable. En effet, une CSP stricte n'autorise que les scripts hachés ou les scripts avec la valeur nonce correcte générée sur le serveur. Les pirates informatiques ne peuvent donc pas exécuter le script sans connaître la valeur nonce correcte pour une réponse donnée.

Pourquoi utiliser une CSP stricte ?

Si votre site dispose déjà d'une CSP semblable à script-src www.googleapis.com, elle n'est probablement pas efficace contre les attaques de script intersites. Ce type de CSP est appelé CSP de liste d'autorisation. Elles nécessitent beaucoup de personnalisation et peuvent être contournées par les pirates informatiques.

Les CSP strictes basées sur des nonces ou des hachages cryptographiques évitent ces écueils.

Structure CSP stricte

Une Content Security Policy stricte de base utilise l'un des en-têtes de réponse HTTP suivants :

CSP stricte basée sur un nonce

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';
Fonctionnement d'une CSP stricte basée sur un nonce

CSP stricte basée sur le hachage

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Les propriétés suivantes rendent une CSP comme celle-ci "stricte" et donc sécurisée :

  • Elle utilise des nonces 'nonce-{RANDOM}' ou des hachages 'sha256-{HASHED_INLINE_SCRIPT}' pour indiquer les balises <script> que le développeur du site considère comme fiables pour l'exécution dans le navigateur de l'utilisateur.
  • Il définit 'strict-dynamic' pour réduire l'effort de déploiement d'une CSP basée sur un nonce ou un hachage en autorisant automatiquement l'exécution des scripts créés par un script de confiance. Cela débloque également l'utilisation de la plupart des bibliothèques et widgets JavaScript tiers.
  • Elle n'est pas basée sur des listes d'autorisation d'URL. Elle ne souffre donc pas des contournements courants de CSP.
  • Elle bloque les scripts intégrés non approuvés, tels que les gestionnaires d'événements intégrés ou les URI javascript:.
  • Il limite object-src pour désactiver les plug-ins dangereux comme Flash.
  • Elle limite base-uri pour bloquer l'injection de balises <base>. Cela empêche les pirates informatiques de modifier l'emplacement des scripts chargés à partir d'URL relatives.

Adopter une CSP stricte

Pour adopter une CSP stricte, vous devez :

  1. Déterminez si votre application doit définir un CSP basé sur un nonce ou un hachage.
  2. Copiez la CSP de la section Structure de CSP stricte et définissez-la comme en-tête de réponse dans l'ensemble de votre application.
  3. Refactorisez les modèles HTML et le code côté client pour supprimer les modèles incompatibles avec la CSP.
  4. Déployez votre CSP.

Vous pouvez utiliser l'audit des bonnes pratiques de Lighthouse (v7.3.0 et versions ultérieures avec l'option --preset=experimental) tout au long de ce processus pour vérifier si votre site dispose d'une CSP et si elle est suffisamment stricte pour être efficace contre les attaques XSS.

Avertissement du rapport Lighthouse indiquant qu'aucun CSP n'a été trouvé en mode forcé.
Si votre site n'a pas de CSP, Lighthouse affiche cet avertissement.

Étape 1 : Déterminez si vous avez besoin d'une CSP basée sur un nonce ou un hachage

Voici comment fonctionnent les deux types de CSP strictes :

CSP basée sur un nonce

Avec une CSP basée sur un nonce, vous générez un nombre aléatoire au moment de l'exécution, vous l'incluez dans votre CSP et vous l'associez à chaque tag de script de votre page. Un pirate informatique ne peut pas inclure ni exécuter de script malveillant sur votre page, car il devrait deviner le nombre aléatoire correct pour ce script. Cela ne fonctionne que si le nombre n'est pas prévisible et qu'il est généré à l'exécution pour chaque réponse.

Utilisez une CSP basée sur un nonce pour les pages HTML affichées sur le serveur. Pour ces pages, vous pouvez créer un nombre aléatoire pour chaque réponse.

CSP basée sur le hachage

Pour une CSP basée sur un hachage, le hachage de chaque tag de script intégré est ajouté à la CSP. Chaque script possède un hachage différent. Un pirate informatique ne peut pas inclure ni exécuter de script malveillant sur votre page, car le hachage de ce script doit se trouver dans votre CSP pour qu'il s'exécute.

Utilisez une CSP basée sur le hachage pour les pages HTML diffusées de manière statique ou les pages qui doivent être mises en cache. Par exemple, vous pouvez utiliser une CSP basée sur un hachage pour les applications Web monopages créées avec des frameworks tels qu'Angular, React ou d'autres, qui sont diffusées de manière statique sans rendu côté serveur.

Étape 2 : Définissez une CSP stricte et préparez vos scripts

Lorsque vous définissez une CSP, plusieurs options s'offrent à vous :

  • Mode rapport uniquement (Content-Security-Policy-Report-Only) ou mode d'application (Content-Security-Policy). En mode rapport uniquement, la CSP ne bloque pas encore les ressources, de sorte que rien ne se casse sur votre site, mais vous pouvez voir les erreurs et obtenir des rapports pour tout ce qui aurait été bloqué. En local, lorsque vous définissez votre CSP, cela n'a pas vraiment d'importance, car les deux modes affichent les erreurs dans la console du navigateur. Le mode d'application peut vous aider à trouver les ressources que votre brouillon de CSP bloque, car le blocage d'une ressource peut donner l'impression que votre page est défectueuse. Le mode "Rapport uniquement" devient plus utile plus tard dans le processus (voir l'étape 5).
  • En-tête ou balise HTML <meta>. Pour le développement local, une balise <meta> peut être plus pratique pour ajuster votre CSP et voir rapidement comment elle affecte votre site. Toutefois :
    • Plus tard, lorsque vous déploierez votre CSP en production, nous vous recommandons de le définir en tant qu'en-tête HTTP.
    • Si vous souhaitez définir votre CSP en mode rapport uniquement, vous devez la définir en tant qu'en-tête, car les balises Meta CSP ne sont pas compatibles avec le mode rapport uniquement.

Option A : CSP basé sur un nonce

Définissez l'en-tête de réponse HTTP Content-Security-Policy suivant dans votre application :

Content-Security-Policy:
  script-src 'nonce-{RANDOM}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Générer un nonce pour la CSP

Un nonce est un nombre aléatoire utilisé une seule fois par chargement de page. Une CSP basée sur un nonce ne peut atténuer les attaques XSS que si les pirates ne peuvent pas deviner la valeur du nonce. Un nonce CSP doit être :

  • Une valeur aléatoire cryptographiquement forte (idéalement d'une longueur de 128 bits ou plus)
  • Généré à nouveau pour chaque réponse
  • Encodage en base64

Voici quelques exemples d'ajout d'un nonce CSP dans des frameworks côté serveur :

const app = express();

app.get('/', function(request, response) {
  // Generate a new random nonce value for every response.
  const nonce = crypto.randomBytes(16).toString("base64");

  // Set the strict nonce-based CSP response header
  const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`;
  response.set("Content-Security-Policy", csp);

  // Every <script> tag in your application should set the `nonce` attribute to this value.
  response.render(template, { nonce: nonce });
});

Ajouter un attribut nonce aux éléments <script>

Avec une CSP basée sur un nonce, chaque élément <script> doit comporter un attribut nonce correspondant à la valeur de nonce aléatoire spécifiée dans l'en-tête de la CSP. Tous les scripts peuvent avoir le même nonce. La première étape consiste à ajouter ces attributs à tous les scripts afin que la CSP les autorise.

Option B : En-tête de réponse CSP basé sur le hachage

Définissez l'en-tête de réponse HTTP Content-Security-Policy suivant dans votre application :

Content-Security-Policy:
  script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
  object-src 'none';
  base-uri 'none';

Pour plusieurs scripts intégrés, la syntaxe est la suivante : 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'.

Charger les scripts sources de manière dynamique

Vous pouvez charger des scripts tiers de manière dynamique à l'aide d'un script intégré.

Exemple d'intégration de vos scripts.
Autorisé par CSP
<script>
  var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js'];

  scripts.forEach(function(scriptUrl) {
    var s = document.createElement('script');
    s.src = scriptUrl;
    s.async = false; // to preserve execution order
    document.head.appendChild(s);
  });
</script>
Pour que ce script s'exécute, vous devez calculer le hachage du script intégré et l'ajouter à l'en-tête de réponse CSP, en remplaçant l'espace réservé {HASHED_INLINE_SCRIPT}. Pour réduire le nombre de hachages, vous pouvez fusionner tous les scripts intégrés dans un seul script. Pour voir une démonstration, consultez cet exemple et son code.
Bloqué par CSP
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
La CSP bloque ces scripts, car ils n'ont pas été ajoutés de manière dynamique et ne comportent pas d'attribut integrity correspondant à une source autorisée.

Points à prendre en compte concernant le chargement des scripts

L'exemple de script intégré ajoute s.async = false pour s'assurer que foo s'exécute avant bar, même si bar se charge en premier. Dans cet extrait, s.async = false ne bloque pas l'analyseur pendant le chargement des scripts, car ceux-ci sont ajoutés de manière dynamique. L'analyseur ne s'arrête que pendant l'exécution des scripts, comme il le ferait pour les scripts async. Toutefois, tenez compte des points suivants concernant cet extrait :

  • Un ou les deux scripts peuvent s'exécuter avant que le téléchargement du document ne soit terminé. Si vous souhaitez que le document soit prêt au moment de l'exécution des scripts, attendez l'événement DOMContentLoaded avant d'ajouter les scripts. Si cela entraîne un problème de performances, car les scripts ne commencent pas à se télécharger assez tôt, utilisez des balises de préchargement plus tôt sur la page.
  • defer = true ne fait rien. Si vous avez besoin de ce comportement, exécutez le script manuellement lorsque cela est nécessaire.

Étape 3 : Refactoriser les modèles HTML et le code côté client

Les gestionnaires d'événements intégrés (tels que onclick="…", onerror="…") et les URI JavaScript (<a href="javascript:…">) peuvent être utilisés pour exécuter des scripts. Cela signifie qu'un pirate informatique qui trouve un bug XSS peut injecter ce type de code HTML et exécuter du code JavaScript malveillant. Une CSP basée sur un nonce ou un hachage interdit l'utilisation de ce type de balisage. Si votre site utilise l'un de ces modèles, vous devrez le refactoriser en utilisant des alternatives plus sûres.

Si vous avez activé la CSP à l'étape précédente, vous pourrez voir les cas de non-respect de la CSP dans la console chaque fois que la CSP bloque un modèle incompatible.

Rapports de non-respect de la CSP dans la console pour les développeurs Chrome.
Erreurs de la console pour le code bloqué.

Dans la plupart des cas, la solution est simple :

Refactoriser les gestionnaires d'événements intégrés

Autorisé par CSP
<span id="things">A thing.</span>
<script nonce="${nonce}">
  document.getElementById('things').addEventListener('click', doThings);
</script>
La CSP autorise les gestionnaires d'événements enregistrés à l'aide de JavaScript.
Bloqué par CSP
<span onclick="doThings();">A thing.</span>
La CSP bloque les gestionnaires d'événements intégrés.

Refactoriser les URI javascript:

Autorisé par CSP
<a id="foo">foo</a>
<script nonce="${nonce}">
  document.getElementById('foo').addEventListener('click', linkClicked);
</script>
La CSP autorise les gestionnaires d'événements enregistrés à l'aide de JavaScript.
Bloqué par CSP
<a href="javascript:linkClicked()">foo</a>
La CSP bloque les URI javascript:.

Supprimer eval() de votre code JavaScript

Si votre application utilise eval() pour convertir des sérialisations de chaînes JSON en objets JS, vous devez refactoriser ces instances en JSON.parse(), qui est également plus rapide.

Si vous ne pouvez pas supprimer toutes les utilisations de eval(), vous pouvez toujours définir une CSP stricte basée sur un nonce, mais vous devez utiliser le mot clé CSP 'unsafe-eval', ce qui rend votre stratégie légèrement moins sécurisée.

Vous trouverez d'autres exemples de ce type de refactorisation dans cet atelier de programmation sur la CSP stricte :

Étape 4 (facultative) : Ajoutez des solutions de remplacement pour prendre en charge les anciennes versions de navigateur

Browser Support

  • Chrome: 52.
  • Edge: 79.
  • Firefox: 52.
  • Safari: 15.4.

Si vous devez prendre en charge des versions de navigateur plus anciennes :

  • L'utilisation de strict-dynamic nécessite l'ajout de https: comme solution de remplacement pour les versions antérieures de Safari. Dans ce cas :
    • Tous les navigateurs compatibles avec strict-dynamic ignorent l'option de remplacement https:. Cela ne réduit donc pas la force de la règle.
    • Dans les anciens navigateurs, les scripts provenant de sources externes ne peuvent se charger que s'ils proviennent d'une origine HTTPS. Cette méthode est moins sécurisée qu'une CSP stricte, mais elle empêche tout de même certaines causes courantes de XSS, comme les injections d'URI javascript:.
  • Pour assurer la compatibilité avec les versions de navigateur très anciennes (plus de quatre ans), vous pouvez ajouter unsafe-inline comme solution de secours. Tous les navigateurs récents ignorent unsafe-inline si un nonce ou un hachage CSP est présent.
Content-Security-Policy:
  script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
  object-src 'none';
  base-uri 'none';

Étape 5 : Déployez votre CSP

Après avoir vérifié que votre CSP ne bloque aucun script légitime dans votre environnement de développement local, vous pouvez déployer votre CSP en préproduction, puis dans votre environnement de production :

  1. (Facultatif) Déployez votre CSP en mode rapport uniquement à l'aide de l'en-tête Content-Security-Policy-Report-Only. Le mode "Rapport uniquement" est utile pour tester une modification potentiellement destructive, comme une nouvelle CSP en production, avant de commencer à appliquer les restrictions de la CSP. En mode "Rapport uniquement", votre CSP n'affecte pas le comportement de votre application, mais le navigateur génère toujours des erreurs de console et des rapports de non-respect lorsqu'il rencontre des modèles incompatibles avec votre CSP. Vous pouvez ainsi voir ce qui aurait été défaillant pour vos utilisateurs finaux. Pour en savoir plus, consultez API Reporting.
  2. Lorsque vous êtes certain que votre CSP ne perturbera pas le fonctionnement de votre site pour les utilisateurs finaux, déployez-le à l'aide de l'en-tête de réponse Content-Security-Policy. Nous vous recommandons de définir votre CSP à l'aide d'un en-tête HTTP côté serveur, car cette méthode est plus sécurisée qu'une balise <meta>. Une fois cette étape terminée, votre CSP commence à protéger votre application contre les attaques XSS.

Limites

Une CSP stricte fournit généralement une couche de sécurité supplémentaire efficace qui permet de limiter les attaques XSS. Dans la plupart des cas, la CSP réduit considérablement la surface d'attaque en rejetant les modèles dangereux tels que les URI javascript:. Toutefois, selon le type de CSP que vous utilisez (nonces, hachages, avec ou sans 'strict-dynamic'), il existe des cas où la CSP ne protège pas aussi bien votre application :

  • Si vous utilisez un nonce pour un script, mais qu'une injection est effectuée directement dans le corps ou le paramètre src de cet élément <script>.
  • S'il existe des injections dans les emplacements des scripts créés de manière dynamique (document.createElement('script')), y compris dans les fonctions de bibliothèque qui créent des nœuds DOM script en fonction des valeurs de leurs arguments. Cela inclut certaines API courantes telles que .html() de jQuery, ainsi que .get() et .post() dans jQuery < 3.0.
  • Si des injections de modèles sont présentes dans d'anciennes applications AngularJS. Un pirate informatique capable d'injecter du code dans un modèle AngularJS peut l'utiliser pour exécuter du code JavaScript arbitraire.
  • Si la règle contient 'unsafe-eval', les injections dans eval(), setTimeout() et quelques autres API rarement utilisées.

Les développeurs et les ingénieurs en sécurité doivent prêter une attention particulière à ces modèles lors des revues de code et des audits de sécurité. Pour en savoir plus sur ces cas, consultez Content Security Policy: A Successful Mess Between Hardening and Mitigation.

Documentation complémentaire