Règle de sécurité du contenu

La stratégie CSP peut réduire considérablement le risque et l'impact des attaques de script intersites dans les navigateurs modernes.

Joe Medley
Joe Medley

Navigateurs pris en charge

  • Chrome: 25
  • Edge : 14.
  • Firefox: 23
  • Safari : 7.

Source

Le modèle de sécurité du Web est basé sur un règlement de même origine. Par exemple, le code de https://mybank.com ne doit avoir accès qu'aux données de https://mybank.com, et https://evil.example.com ne doit jamais y avoir accès. En théorie, chaque origine est isolée du reste du Web, ce qui offre aux développeurs un bac à sable sécurisé dans lequel créer. En pratique, cependant, les pirates informatiques ont trouvé plusieurs façons de subvertir le système.

Les attaques de script intersites (XSS), par exemple, contournent la règle de même origine en incitant un site à diffuser du code malveillant avec le contenu prévu. Il s'agit d'un problème majeur, car les navigateurs font confiance à tout le code qui s'affiche sur une page comme faisant légitimement partie de l'origine de sécurité de cette page. L'aide-mémoire XSS est un exemple ancien mais représentatif des méthodes qu'un pirate informatique pourrait utiliser pour violer cette confiance en injectant du code malveillant. Si un pirate informatique parvient à injecter n'importe quel code, il compromet la session utilisateur et accède à des informations privées.

Cette page présente la stratégie Content Security Policy (CSP) pour réduire le risque et l'impact des attaques XSS dans les navigateurs modernes.

Composants du CSP

Pour implémenter un CSP efficace, procédez comme suit :

  • Utilisez des listes d'autorisation pour indiquer au client ce qui est autorisé et ce qui ne l'est pas.
  • Découvrez les directives disponibles.
  • Découvrez les mots clés qu'ils utilisent.
  • Limitez l'utilisation du code intégré et de eval().
  • Signalez les cas de non-respect des règles à votre serveur avant de les appliquer.

Listes d'autorisation sources

Les attaques XSS exploitent l'incapacité du navigateur à faire la distinction entre le script qui fait partie de votre application et le script qui a été injecté de manière malveillante par un tiers. Par exemple, le bouton Google +1 en bas de cette page charge et exécute le code de https://apis.google.com/js/plusone.js dans le contexte de l'origine de cette page. Nous faisons confiance à ce code, mais nous ne pouvons pas nous attendre à ce que le navigateur détermine lui-même que le code de apis.google.com peut être exécuté en toute sécurité, alors que celui de apis.evil.example.com ne l'est probablement pas. Le navigateur télécharge et exécute volontiers tout code qu'une page demande, quelle que soit la source.

L'en-tête HTTP Content-Security-Policy de la CSP vous permet de créer une liste d'autorisation de sources de contenu fiables, et indique au navigateur de n'exécuter ou d'afficher que des ressources provenant de ces sources. Même si un pirate informatique parvient à trouver une faille pour injecter un script, celui-ci ne correspondra pas à la liste d'autorisation et ne sera donc pas exécuté.

Nous faisons confiance à apis.google.com pour fournir du code valide, et nous nous faisons confiance pour faire de même. Voici un exemple de stratégie qui n'autorise l'exécution des scripts que lorsqu'ils proviennent de l'une de ces deux sources :

Content-Security-Policy: script-src 'self' https://apis.google.com

script-src est une directive qui contrôle un ensemble de droits liés aux scripts pour une page. Cet en-tête 'self' est une source de script valide, et https://apis.google.com en est une autre. Le navigateur peut désormais télécharger et exécuter du code JavaScript à partir de apis.google.com via HTTPS, ainsi que depuis l'origine de la page actuelle, mais pas depuis une autre origine. Si un pirate informatique injecte du code dans votre site, le navigateur génère une erreur et n'exécute pas le script injecté.

Erreur de console : le chargement du script "http://evil.example.com/evil.js" a été refusé, car il ne respecte pas la directive de la Content Security Policy suivante : script-src "self" https://apis.google.com
La console affiche une erreur lorsqu'un script tente de s'exécuter à partir d'une origine ne figurant pas sur la liste d'autorisation.

La stratégie s'applique à un large éventail de ressources

Le CSP fournit un ensemble de directives de stratégie qui permettent de contrôler précisément les ressources qu'une page est autorisée à charger, y compris script-src de l'exemple précédent.

La liste suivante décrit le reste des directives de ressources au niveau 2. Une spécification de niveau 3 a été rédigée, mais elle n'est largement pas implémentée dans les principaux navigateurs.

base-uri
Limite les URL pouvant apparaître dans l'élément <base> d'une page.
child-src
Liste les URL des nœuds de calcul et les contenus des cadres intégrés. Par exemple, child-src https://youtube.com permet d'intégrer des vidéos YouTube, mais pas d'autres sources.
connect-src
Limite les origines auxquelles vous pouvez vous connecter à l'aide de XHR, de WebSockets et d'EventSource.
font-src
Spécifie les origines pouvant diffuser des polices Web. Par exemple, vous pouvez autoriser les polices Web de Google à l'aide de font-src https://themes.googleusercontent.com.
form-action
Indique les points de terminaison valides pour l'envoi à partir des balises <form>.
frame-ancestors
Spécifie les sources pouvant intégrer la page actuelle. Cette directive s'applique aux balises <frame>, <iframe>, <embed> et <applet>. Il ne peut pas être utilisé dans les balises <meta> ni pour les ressources HTML.
frame-src
Cette directive a été abandonnée au niveau 2, mais a été restaurée au niveau 3. S'il n'est pas présent, le navigateur utilise child-src.
img-src
Définit les origines à partir desquelles les images peuvent être chargées.
media-src
Limite les origines autorisées à diffuser des contenus vidéo et audio.
object-src
Permet de contrôler Flash et d'autres plug-ins.
plugin-types
Limite les types de plug-ins qu'une page peut appeler.
report-uri
Spécifie une URL à laquelle le navigateur envoie des rapports lorsqu'une stratégie de sécurité du contenu est enfreinte. Cette directive ne peut pas être utilisée dans les balises <meta>.
style-src
Limite les origines à partir desquelles une page peut utiliser des feuilles de style.
upgrade-insecure-requests
Indique aux agents utilisateur de réécrire les schémas d'URL en remplaçant HTTP par HTTPS. Cette directive s'applique aux sites Web comportant un grand nombre d'anciennes URL à réécrire.
worker-src
Directive de niveau 3 du CSP qui limite les URL pouvant être chargées en tant que worker, worker partagé ou service worker. Depuis juillet 2017, cette directive a des implémentations limitées.

Par défaut, le navigateur charge la ressource associée à partir de n'importe quelle origine, sans restriction, sauf si vous définissez une stratégie avec une directive spécifique. Pour remplacer la valeur par défaut, spécifiez une directive default-src. Cette directive définit les valeurs par défaut de toute directive non spécifiée se terminant par -src. Par exemple, si vous définissez default-src sur https://example.com et que vous ne spécifiez pas de directive font-src, vous ne pouvez charger des polices qu'à partir de https://example.com.

Les directives suivantes n'utilisent pas default-src comme solution de remplacement. N'oubliez pas que l'échec de leur définition revient à autoriser quoi que ce soit:

  • base-uri
  • form-action
  • frame-ancestors
  • plugin-types
  • report-uri
  • sandbox

Syntaxe de base du CSP

Pour utiliser des directives CSP, listez-les dans l'en-tête HTTP, en les séparant par des deux-points. Veillez à lister toutes les ressources requises d'un type spécifique dans une seule directive, comme suit :

script-src https://host1.com https://host2.com

Vous trouverez ci-dessous un exemple de plusieurs directives, dans ce cas pour une application Web qui charge toutes ses ressources à partir d'un réseau de diffusion de contenu sur https://cdn.example.net et qui n'utilise pas de contenu ni de plug-in encadrés :

Content-Security-Policy: default-src https://cdn.example.net; child-src 'none'; object-src 'none'

Détails de mise en œuvre

Les navigateurs modernes acceptent l'en-tête Content-Security-Policy sans préfixe. Il s'agit de l'en-tête recommandé. Les en-têtes X-WebKit-CSP et X-Content-Security-Policy que vous pouvez voir dans les tutoriels en ligne sont obsolètes.

La CSP est définie page par page. Vous devez envoyer l'en-tête HTTP avec chaque réponse que vous souhaitez protéger. Vous pouvez ainsi adapter la règle à des pages spécifiques en fonction de leurs besoins spécifiques. Par exemple, si un ensemble de pages de votre site comporte un bouton +1, tandis que d'autres n'en ont pas, vous pouvez autoriser le code du bouton à se charger uniquement lorsque cela est nécessaire.

La liste source de chaque directive est flexible. Vous pouvez spécifier des sources par schéma (data:, https:), ou en fonction de leur spécificité, du nom d'hôte uniquement (example.com, qui correspond à n'importe quelle origine sur cet hôte : n'importe quel schéma, n'importe quel port) à un URI complet (https://example.com:443, qui ne correspond qu'à HTTPS, qu'à example.com et qu'au port 443). Les caractères génériques sont acceptés, mais uniquement en tant que schéma, port ou à la position la plus à gauche du nom d'hôte : *://*.example.com:* correspond à tous les sous-domaines de example.com (mais pas example.com lui-même), à l'aide de n'importe quel schéma, sur n'importe quel port.

La liste de sources accepte également quatre mots clés :

  • 'none' ne correspond à rien.
  • 'self' correspond à l'origine actuelle, mais pas à ses sous-domaines.
  • 'unsafe-inline' permet d'intégrer JavaScript et CSS. Pour en savoir plus, consultez Éviter le code intégré.
  • 'unsafe-eval' permet d'utiliser des mécanismes de conversion texte-JavaScript tels que eval. Pour en savoir plus, consultez la section Éviter eval().

Ces mots clés doivent être placés entre guillemets simples. Par exemple, script-src 'self' (avec guillemets) autorise l'exécution de JavaScript à partir de l'hôte actuel. script-src self (sans guillemets) autorise JavaScript à partir d'un serveur nommé "self" (et pas à partir de l'hôte actuel), ce qui n'est probablement pas ce que vous vouliez dire.

Créez un bac à sable pour vos pages

Voici une autre directive qui mérite d'être évoquée: sandbox. Il est un peu différent des autres que nous avons examinés, car il impose des restrictions sur les actions que la page peut effectuer plutôt que sur les ressources qu'elle peut charger. Si l'instruction sandbox est présente, la page est traitée comme si elle avait été chargée dans un élément <iframe> avec un attribut sandbox. Cela peut avoir un large éventail d'effets sur la page: forcer la page à avoir une origine unique et empêcher l'envoi de formulaire, entre autres. Cette opération dépasse un peu le cadre de cette page, mais vous trouverez des informations complètes sur les attributs de bac à sable valides dans la section "Bac à sable" de la spécification HTML5.

La balise Meta

Le mécanisme de diffusion privilégié des CSP est un en-tête HTTP. Toutefois, il peut être utile de définir une règle sur une page directement dans le balisage. Pour ce faire, utilisez une balise <meta> avec un attribut http-equiv :

<meta http-equiv="Content-Security-Policy" content="default-src https://cdn.example.net; child-src 'none'; object-src 'none'">

Cette option ne peut pas être utilisée pour frame-ancestors, report-uri ou sandbox.

Éviter le code intégré

Aussi puissantes que soient les listes d'autorisation basées sur l'origine utilisées dans les directives CSP, elles ne peuvent pas résoudre la plus grande menace posée par les attaques XSS : l'injection de script en ligne. Si un pirate informatique peut injecter une balise de script contenant directement une charge utile malveillante (comme <script>sendMyDataToEvilDotCom()</script>), le navigateur n'a aucun moyen de la distinguer d'une balise de script intégrée légitime. CSP résout ce problème en interdisant complètement le script intégré.

Cette interdiction concerne non seulement les scripts intégrés directement dans les balises script, mais aussi les gestionnaires d'événements intégrés et les URL javascript:. Vous devez déplacer le contenu des balises script dans un fichier externe, et remplacer les URL javascript: et <a ... onclick="[JAVASCRIPT]"> par les appels addEventListener() appropriés. Par exemple, vous pouvez réécrire le code suivant à partir de :

<script>
    function doAmazingThings() {
    alert('YOU ARE AMAZING!');
    }
</script>
<button onclick='doAmazingThings();'>Am I amazing?</button>

à quelque chose comme :

<!-- amazing.html -->
<script src='amazing.js'></script>
<button id='amazing'>Am I amazing?</button>
// amazing.js
function doAmazingThings() {
    alert('YOU ARE AMAZING!');
}
document.addEventListener('DOMContentLoaded', function () {
    document.getElementById('amazing')
    .addEventListener('click', doAmazingThings);
});

Le code réécrit est non seulement compatible avec le CSP, mais aussi conforme aux bonnes pratiques de conception Web. Le code JavaScript intégré mélange la structure et le comportement de manière à rendre le code déroutant. Il est également plus compliqué à mettre en cache et à compiler. En déplaçant votre code vers des ressources externes, vous améliorez les performances de vos pages.

Il est également fortement recommandé de déplacer les balises et attributs style intégrés vers des feuilles de style externes pour protéger votre site contre les attaques d'exfiltration de données basées sur CSS.

Autoriser temporairement les scripts et styles intégrés

Vous pouvez activer les scripts et les styles intégrés en ajoutant 'unsafe-inline' en tant que source autorisée dans une instruction script-src ou style-src. Le niveau 2 du CSP vous permet également d'ajouter des scripts intégrés spécifiques à votre liste d'autorisation à l'aide d'un nonce cryptographique (nombre utilisé une seule fois) ou d'un hachage comme suit.

Pour utiliser un nonce, attribuez un attribut nonce à votre balise de script. Sa valeur doit correspondre à l'une des sources approuvées de la liste. Exemple :

<script nonce="EDNnf03nceIOfn39fn3e9h3sdfa">
    // Some inline code I can't remove yet, but need to as soon as possible.
</script>

Ajoutez le nonce à votre directive script-src après le mot clé nonce- :

Content-Security-Policy: script-src 'nonce-EDNnf03nceIOfn39fn3e9h3sdfa'

Les nonces doivent être générés à nouveau pour chaque requête de page et ne doivent pas être devinés.

Les hachages fonctionnent de manière similaire. Au lieu d'ajouter du code à la balise de script, créez un hachage SHA du script lui-même et ajoutez-le à la directive script-src. Par exemple, si votre page contenait ceci :

<script>alert('Hello, world.');</script>

Vos règles doivent contenir les éléments suivants:

Content-Security-Policy: script-src 'sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng='

Le préfixe sha*- spécifie l'algorithme qui génère le hachage. L'exemple précédent utilise sha256-, mais le CSP est également compatible avec sha384- et sha512-. Lors de la génération du hachage, omettez les balises <script>. La capitalisation et les espaces sont importants, y compris les espaces de début et de fin.

Des solutions permettant de générer des hachages SHA sont disponibles dans toutes les langues. À partir de la version 40 de Chrome, vous pouvez ouvrir les outils de développement, puis actualiser la page. L'onglet "Console" affiche les messages d'erreur avec le hachage SHA-256 correct pour chacun de vos scripts intégrés.

Éviter la requête eval()

Même si un pirate informatique ne peut pas injecter de script directement, il peut être en mesure de duper votre application pour qu'elle convertisse le texte saisi en code JavaScript exécutable et l'exécute en son nom. eval(), new Function(), setTimeout([string], …) et setInterval([string], ...) sont tous des vecteurs que les pirates informatiques peuvent utiliser pour exécuter du code malveillant via du texte injecté. La réponse par défaut de CSP à ce risque consiste à bloquer complètement tous ces vecteurs.

Cela aura les conséquences suivantes sur la façon dont vous créez des applications:

  • Vous devez analyser le JSON à l'aide de JSON.parse intégré, au lieu de vous appuyer sur eval. Les opérations JSON sécurisées sont disponibles dans tous les navigateurs depuis IE8.
  • Vous devez réécrire tous les appels setTimeout ou setInterval que vous effectuez à l'aide de fonctions intégrées au lieu de chaînes. Par exemple, si votre page contient les éléments suivants :

    setTimeout("document.querySelector('a').style.display = 'none';", 10);
    

    Réécrivez-la comme suit :

    setTimeout(function () {
        document.querySelector('a').style.display = 'none';
    }, 10);
      ```
    
  • Évitez d'utiliser des modèles intégrés au moment de l'exécution. De nombreuses bibliothèques de modèles utilisent souvent new Function() pour accélérer la génération de modèles au moment de l'exécution, ce qui permet d'évaluer le texte malveillant. Certains frameworks sont compatibles avec le CSP dès le départ, et reviennent à un analyseur robuste en l'absence de eval. La directive ng-csp d'AngularJS en est un bon exemple. Toutefois, nous vous recommandons d'utiliser un langage de création de modèles qui propose une précompilation, comme Handlebars. La précompilation de vos modèles peut rendre l'expérience utilisateur encore plus rapide que l'implémentation d'exécution la plus rapide, tout en rendant votre site plus sécurisé.

Si eval() ou d'autres fonctions de conversion de texte en JavaScript sont essentielles à votre application, vous pouvez les activer en ajoutant 'unsafe-eval' en tant que source autorisée dans une directive script-src. Nous vous déconseillons vivement de le faire en raison du risque d'injection de code qu'il présente.

Signaler un cas de non-respect des règles

Pour avertir le serveur des bugs susceptibles d'autoriser une injection malveillante, vous pouvez demander au navigateur d'POST envoyer des rapports de non-respect au format JSON à un emplacement spécifié dans une directive report-uri :

Content-Security-Policy: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

Ces rapports se présentent comme suit:

{
    "csp-report": {
    "document-uri": "http://example.org/page.html",
    "referrer": "http://evil.example.com/",
    "blocked-uri": "http://evil.example.com/evil.js",
    "violated-directive": "script-src 'self' https://apis.google.com",
    "original-policy": "script-src 'self' https://apis.google.com; report-uri http://example.org/my_amazing_csp_report_parser"
    }
}

Le rapport contient des informations utiles pour identifier la cause d'un non-respect des règles, y compris la page concernée (document-uri), le referrer de cette page, la ressource qui ne respecte pas le règlement de la page (blocked-uri), la directive spécifique qu'elle ne respecte pas (violated-directive) et le règlement complet de la page (original-policy).

Rapports uniquement

Si vous débutez avec le CSP, nous vous recommandons d'utiliser le mode "Rapports uniquement" pour évaluer l'état de votre application avant de modifier votre règle. Pour ce faire, au lieu d'envoyer un en-tête Content-Security-Policy, envoyez un en-tête Content-Security-Policy-Report-Only :

Content-Security-Policy-Report-Only: default-src 'self'; ...; report-uri /my_amazing_csp_report_parser;

La règle spécifiée en mode "Rapport uniquement" ne bloque pas les ressources limitées, mais envoie des rapports de non-respect à l'emplacement que vous spécifiez. Vous pouvez même envoyer les deux en-têtes pour appliquer une règle tout en en surveillant une autre. C'est un excellent moyen de tester les modifications apportées à votre CSP tout en appliquant votre règle actuelle: activez la création de rapports pour une nouvelle règle, surveillez les rapports de non-respect et corrigez les bugs, puis commencez à l'appliquer dès que la nouvelle règle vous convient.

Utilisation réelle

La première étape de la création d'une règle pour votre application consiste à évaluer les ressources qu'elle charge. Une fois que vous avez compris la structure de votre application, créez une règle en fonction de ses exigences. Les sections suivantes présentent quelques cas d'utilisation courants et le processus de prise en charge de ces cas conformément aux consignes du CSP.

Widgets de réseaux sociaux

  • Le bouton "J'aime" de Facebook propose plusieurs options d'implémentation. Nous vous recommandons d'utiliser la version de <iframe> pour qu'elle reste en bac à sable du reste de votre site. Il a besoin d'une directive child-src https://facebook.com pour fonctionner correctement.
  • Le bouton Tweet de X repose sur l'accès à un script. Déplacez le script qu'il fournit dans un fichier JavaScript externe et utilisez la directive script-src https://platform.twitter.com; child-src https://platform.twitter.com.
  • Les autres plates-formes ont des exigences similaires et peuvent être traitées de la même manière. Pour tester ces ressources, nous vous recommandons de définir un default-src sur 'none' et de surveiller votre console pour déterminer les ressources que vous devrez activer.

Pour utiliser plusieurs widgets, combinez les directives comme suit :

script-src https://apis.google.com https://platform.twitter.com; child-src https://plusone.google.com https://facebook.com https://platform.twitter.com

Verrouiller

Pour certains sites Web, vous devez vous assurer que seules les ressources locales peuvent être chargées. L'exemple suivant développe un CSP pour un site bancaire, en commençant par une stratégie par défaut qui bloque tout (default-src 'none').

Le site charge toutes les images, tous les styles et tous les scripts à partir d'un CDN à l'adresse https://cdn.mybank.net, et se connecte à https://api.mybank.com/ à l'aide de XHR pour récupérer les données. Il utilise des cadres, mais uniquement pour les pages locales du site (pas d'origines tierces). Le site ne comporte pas de Flash, de polices ni d'éléments supplémentaires. L'en-tête CSP le plus restrictif qu'il peut envoyer est le suivant :

Content-Security-Policy: default-src 'none'; script-src https://cdn.mybank.net; style-src https://cdn.mybank.net; img-src https://cdn.mybank.net; connect-src https://api.mybank.com; child-src 'self'

SSL uniquement

Voici un exemple de CSP pour un administrateur de forum qui souhaite s'assurer que toutes les ressources de son forum ne sont chargées qu'à l'aide de canaux sécurisés, mais qui n'a pas d'expérience en codage et ne dispose pas des ressources nécessaires pour réécrire le logiciel de forum tiers rempli de scripts et de styles intégrés :

Content-Security-Policy: default-src https:; script-src https: 'unsafe-inline'; style-src https: 'unsafe-inline'

Bien que https: soit spécifié dans default-src, les directives de script et de style n'héritent pas automatiquement de cette source. Chaque directive écrase la valeur par défaut de ce type de ressource spécifique.

Développement standard CSP

Le niveau 2 de la règle de sécurité du contenu est une norme recommandée par le W3C. Le groupe de travail sur la sécurité des applications Web du W3C développe la prochaine itération de la spécification, la Content Security Policy Level 3.

Pour suivre la discussion sur ces fonctionnalités à venir, consultez les archives de la liste de diffusion public-webappsec@.