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

Navigateurs pris en charge

  • 52
  • 79
  • 52
  • 15,4

Source

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

Content Security Policy (CSP) est une couche de sécurité supplémentaire qui permet d'atténuer 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 un CSP basé sur des nonces ou des hachages pour atténuer le trafic XSS, plutôt que les CSP couramment utilisés basés sur une liste d'autorisation de l'hôte qui laissent souvent la page exposée au XSS, car ils peuvent être contournés dans la plupart des configurations.

Terme clé: un nonce est un nombre aléatoire utilisé une seule fois 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 stratégie de sécurité du contenu 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 détectent 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 autorise uniquement 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 le nonce correct pour une réponse donnée.

Pourquoi utiliser une CSP stricte ?

Si votre site dispose déjà d'une CSP ressemblant à script-src www.googleapis.com, elle n'est probablement pas efficace contre les attaques intersites. Ce type de CSP est appelé CSP par 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 hachages cryptographiques évitent ces pièges.

Structure CSP stricte

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

CSP stricte basée sur les non-ce

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

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:

  • Il utilise des nonces 'nonce-{RANDOM}' ou des hachages 'sha256-{HASHED_INLINE_SCRIPT}' pour indiquer les balises <script> approuvées par le développeur du site qui doivent s'exécuter dans le navigateur de l'utilisateur.
  • Il définit 'strict-dynamic' pour réduire les efforts liés au 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 permet également de débloquer l'utilisation de la plupart des bibliothèques et des widgets JavaScript tiers.
  • Il n'est pas basé sur les listes d'autorisation d'URL. Il ne subit donc pas de contournements courants des 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 à la désactivation des plug-ins dangereux tels que 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 une CSP basée sur un nonce ou un hachage.
  2. Copiez la CSP à partir de la section Structure CSP stricte et définissez-la comme en-tête de réponse dans votre application.
  3. Refactorisez les modèles HTML et le code côté client pour supprimer les formats incompatibles avec CSP.
  4. Déployez votre CSP.

Tout au long de ce processus, vous pouvez utiliser l'audit des bonnes pratiques Lighthouse (à partir de la version 7.3.0 avec l'indicateur --preset=experimental) pour vérifier si votre site dispose d'une CSP et s'il est suffisamment strict pour contrer les failles XSS.

Un rapport Lighthouse signale qu&#39;aucune CSP n&#39;a été trouvée en mode application forcée.
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 stricte:

CSP basée sur Nonce

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

Utilisez une CSP basée sur des nonces 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 le hachage, le hachage de chaque tag de script intégré est ajouté à CSP. Chaque script a un hachage différent. Un pirate informatique ne peut pas inclure ni exécuter un script malveillant sur votre page, car le hachage de ce script doit figurer dans votre CSP pour s'exécuter.

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 le hachage pour les applications Web monopages créées à l'aide de 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 Application forcée (Content-Security-Policy). En mode Rapport uniquement, le CSP ne bloque pas encore les ressources. Par conséquent, rien sur votre site ne plante, mais vous pouvez voir les erreurs et générer des rapports sur tout ce qui aurait été bloqué. En local, lorsque vous configurez votre CSP, cela n'a pas vraiment d'importance, car les deux modes affichent les erreurs dans la console du navigateur. Le mode application forcée peut vous aider à trouver des ressources bloquées par votre brouillon de CSP, car le blocage d'une ressource peut rendre votre page défaillante. Le mode Rapport uniquement devient particulièrement utile plus tard dans le processus (voir l'étape 5).
  • En-tête ou balise HTML <meta>. Pour le développement local, il peut être plus pratique d'utiliser une balise <meta> pour ajuster votre CSP et voir rapidement son impact sur votre site. Toutefois :
    • Par la suite, lors du déploiement de 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 le 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ée sur 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 CSP

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

  • Une valeur aléatoire forte de manière cryptographique (idéalement, une longueur de 128 bits ou plus)
  • Nouvelle génération 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 des nonces, chaque élément <script> doit comporter un attribut nonce qui correspond à la valeur de nonce aléatoire spécifiée dans l'en-tête 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 dynamiquement des scripts sources

Étant donné que le hachage CSP n'est compatible avec les navigateurs que pour les scripts intégrés, vous devez charger tous les scripts tiers de manière dynamique à l'aide d'un script intégré. Le hachage des scripts provenant de sources n'est pas compatible avec tous les navigateurs.

Exemple d'intégration de 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 permettre à ce script de s'exécuter, vous devez calculer son hachage et l'ajouter à l'en-tête de réponse de 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 en un seul script. Pour voir comment cela fonctionne, reportez-vous à cet exemple et à son code.
Bloquée par CSP
<script src="https://example.org/foo.js"></script>
<script src="https://example.org/bar.js"></script>
CSP bloque ces scripts, car seuls les scripts intégrés peuvent être hachés.

Remarques concernant le chargement des scripts

L'exemple de script intégré ajoute s.async = false pour garantir que foo s'exécute avant bar, même si bar est chargé en premier. Dans cet extrait de code, s.async = false ne bloque pas l'analyseur pendant le chargement des scripts, car ils sont ajoutés de manière dynamique. L'analyseur ne s'arrête que pendant l'exécution des scripts, comme pour les scripts async. Toutefois, avec cet extrait, gardez à l'esprit les points suivants:

  • L'un ou les deux scripts peuvent s'exécuter avant la fin du téléchargement du document. 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 pose problème parce que le téléchargement des scripts ne commence pas assez tôt, utilisez des tags de préchargement plus tôt sur la page.
  • defer = true ne fait rien. Si nécessaire, exécutez le script manuellement si nécessaire.

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

Vous pouvez exécuter des scripts à l'aide de gestionnaires d'événements intégrés (tels que onclick="…" et onerror="…") et d'URI JavaScript (<a href="javascript:…">). 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 formats, vous devrez les refactoriser afin de les rendre plus sûrs.

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

Rapports sur les cas de non-respect CSP dans la console pour les développeurs Chrome.
Erreurs de la console liées au 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>
CSP autorise les gestionnaires d'événements enregistrés à l'aide de JavaScript.
Bloquée par CSP
<span onclick="doThings();">A thing.</span>
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>
CSP autorise les gestionnaires d'événements enregistrés à l'aide de JavaScript.
Bloquée par CSP
<a href="javascript:linkClicked()">foo</a>
CSP bloque les URI JavaScript.

Supprimer eval() de votre JavaScript

Si votre application utilise eval() pour convertir les sérialisations de chaînes JSON en objets JS, vous devez refactoriser ces instances en JSON.parse(), ce 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 ces exemples et d'autres d'exemples d'une telle refactorisation dans cet atelier de programmation CSP strict:

Étape 4 (facultative): Ajoutez des créations de remplacement pour assurer la compatibilité avec les anciennes versions du navigateur

Navigateurs pris en charge

  • 52
  • 79
  • 52
  • 15,4

Source

Si vous devez utiliser d'anciennes versions de navigateur:

  • L'utilisation de strict-dynamic nécessite l'ajout de https: comme solution de secours pour les versions antérieures de Safari. Si vous effectuez cette opération :
    • Tous les navigateurs compatibles avec strict-dynamic ignorent la création de remplacement https:. Par conséquent, la force de la règle n'est pas réduite.
    • Dans les anciens navigateurs, les scripts 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, telles que les injections d'URI javascript:.
  • Pour assurer la compatibilité avec les versions de navigateur très anciennes (4 ans et plus), vous pouvez ajouter unsafe-inline en remplacement. 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éployer 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 la déployer 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 pratique pour tester une modification potentiellement destructive comme une nouvelle CSP en production avant de commencer à appliquer les restrictions CSP. En mode Rapport uniquement, votre CSP n'affecte pas le comportement de votre application. Toutefois, le navigateur génère tout de même des erreurs et des rapports de non-respect de la console lorsqu'il rencontre des modèles incompatibles avec votre CSP. Vous pouvez ainsi identifier les problèmes pour vos utilisateurs finaux. Pour en savoir plus, consultez la page API Reporting.
  2. Lorsque vous êtes sûr que votre CSP n'affectera pas votre site pour vos utilisateurs finaux, déployez-la à 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 il est plus sécurisé qu'un tag <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 renforcée qui permet de limiter les attaques XSS. Dans la plupart des cas, 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'), CSP ne protège pas non plus votre application dans certains cas:

  • Si vous noncez un script, mais qu'une injection est effectuée directement dans le corps ou dans le paramètre src de cet élément <script>.
  • En cas d'injections dans les emplacements des scripts créés dynamiquement (document.createElement('script')), y compris dans toutes 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 disponibles dans d'anciennes applications AngularJS Un pirate informatique pouvant injecter dans un modèle AngularJS peut l'utiliser pour exécuter du code JavaScript arbitraire.
  • Si la règle contient 'unsafe-eval', des injections dans eval(), setTimeout() et quelques autres API rarement utilisées.

Les développeurs et les ingénieurs en sécurité doivent accorder une attention particulière à ces modèles lors des revues de code et des audits de sécurité. Pour en savoir plus à ce sujet, consultez Content Security Policy: un gâchis réussi entre renforcement et atténuation.

Complément d'informations