Sécurité
Content-Security-Policy expliquée : directives, unsafe-inline, nonces et déploiement sans risque
La CSP est l'en-tête de sécurité le plus puissant, et le plus facile à mal configurer. Les directives qui comptent, comment les nonces et les hashes remplacent 'unsafe-inline', et un plan de déploiement qui ne cassera pas votre site.
Sur cette page
Une Content-Security-Policy (CSP) est un en-tête de réponse qui indique au navigateur d'où une page peut charger des scripts, des styles, des images, des cadres et d'autres ressources. Son rôle principal est de limiter les dégâts : si un attaquant parvient à injecter un <script> dans votre page en exploitant une faille de cross-site scripting (XSS), une bonne politique empêche le navigateur de l'exécuter.
Le problème, c'est qu'« une CSP » et « une bonne CSP » sont deux choses très différentes. Un en-tête truffé de jokers réussit un contrôle de présence et ne protège presque rien. Ce guide explique comment faire la différence.
Comment se construit une politique
Une politique est une liste de directives séparées par des points-virgules. Chaque directive désigne un type de ressource et les sources autorisées pour celui-ci : Content-Security-Policy: default-src 'self'; img-src 'self' https://images.example-cdn.com; object-src 'none'.
Les sources peuvent être 'self' (votre propre origine), des origines précises comme https://js.stripe.com, des schémas comme https: ou data:, des mots-clés comme 'none', ou encore des nonces et des hashes, expliqués plus bas. Les mots-clés s'écrivent entre apostrophes droites ; les origines, non.
Les directives les plus importantes
default-src: la valeur de repli pour la plupart des types de ressources que vous ne mentionnez pas séparément.script-src: la provenance autorisée des scripts. C'est la directive qui protège réellement contre le XSS : elle mérite donc le plus grand soin.style-src,img-src,font-src,connect-src(fetch, XHR, WebSockets),frame-srcetmedia-src: les autres types de ressources.object-src 'none': bloque les plugins tels que<object>et<embed>, une voie d'exécution de scripts ancienne mais encore citée.base-uri 'self'ou'none': empêche une balise<base>injectée de modifier la destination des URL de scripts relatives.frame-ancestors: les sites autorisés à intégrer votre page dans un cadre. C'est le remplaçant moderne de X-Frame-Options.form-action: les destinations vers lesquelles les formulaires peuvent être envoyés.upgrade-insecure-requests: demande au navigateur de charger en HTTPS les sous-ressources enhttp://.report-to/report-uri: l'adresse à laquelle le navigateur envoie les rapports de violation.
Ce qui affaiblit une politique
'unsafe-inline'
Dans script-src, 'unsafe-inline' autorise tous les blocs <script> en ligne ainsi que les gestionnaires d'événements en ligne tels que onclick. C'est précisément ce qu'injecte une attaque XSS : une politique qui autorise les scripts avec 'unsafe-inline' ne la ralentit donc guère. Les navigateurs qui prennent en charge les nonces ou les hashes ignorent 'unsafe-inline' dès que l'un d'eux est présent, ce qui explique qu'on le conserve parfois, à côté d'un nonce, comme solution de repli pour les très vieux navigateurs.
'unsafe-eval'
'unsafe-eval' autorise eval(), new Function() et les arguments sous forme de chaîne passés à setTimeout. Il transforme des données en code, ce qui est risqué. Certaines bibliothèques et certains moteurs de gabarits anciens en ont besoin ; les builds modernes, généralement pas.
Les sources trop larges
*, https:, http: ou data: dans script-src autorisent des scripts venus d'à peu près n'importe où : un attaquant peut donc héberger le sien sur n'importe quel serveur HTTPS. Les CDN populaires posent un problème plus subtil : autoriser l'hôte entier d'un CDN public permet à un attaquant de charger n'importe quelle bibliothèque qui y est hébergée, y compris d'anciennes versions dont les contournements sont connus.
Report-Only seul
Content-Security-Policy-Report-Only signale les violations, mais ne bloque rien. C'est la bonne façon de commencer, et le mauvais endroit où s'arrêter.
Nonces et hashes : comment se passer de 'unsafe-inline'
Un nonce est une valeur aléatoire que votre serveur génère pour chaque réponse. Vous la placez dans l'en-tête (script-src 'nonce-R4nd0mV4lue') et sur chaque balise script en laquelle vous avez confiance : <script nonce="R4nd0mV4lue">. Le navigateur n'exécute que les scripts qui portent le nonce correspondant. Un script injecté ne connaît pas la valeur : il est donc bloqué. Le nonce doit être imprévisible et différent à chaque réponse ; un nonce fixe ne vaut pas mieux que 'unsafe-inline'.
Un hash autorise un script en ligne précis, identifié par l'empreinte SHA-256, SHA-384 ou SHA-512 de son contenu : script-src 'sha256-…'. Les hashes conviennent aux pages statiques, pour lesquelles un nonce par réponse n'est pas réaliste ; la moindre modification du script change le hash.
L'ajout de 'strict-dynamic' permet aux scripts que vous avez approuvés par un nonce ou un hash d'en charger d'autres, et demande aux navigateurs compatibles d'ignorer les listes d'hôtes autorisés. Une CSP dite stricte devient ainsi réaliste, même pour les sites qui utilisent des gestionnaires de balises et des widgets : script-src 'nonce-{random}' 'strict-dynamic'; object-src 'none'; base-uri 'none'.
Voyez comment se lit votre CSP
L'outil gratuit de vérification de la sécurité de Rudra analyse votre Content-Security-Policy directive par directive et signale 'unsafe-inline', 'unsafe-eval', les sources de scripts trop larges et l'absence d'object-src ou de base-uri.
Un plan de déploiement qui ne cassera pas votre site
- Inventoriez ce que charge la page. Ouvrez les outils de développement sur vos pages clés (accueil, connexion, tunnel de commande, pages avec contenus intégrés) et notez l'origine de chaque script, style, police, cadre et API.
- Envoyez une politique en Report-Only. Commencez par quelque chose comme
default-src 'self'; script-src 'self' 'nonce-{random}'; object-src 'none'; base-uri 'self'dansContent-Security-Policy-Report-Only. Les violations apparaissent dans la console du navigateur, ainsi qu'à votre point de collecte des rapports si vous en avez défini un. - Corrigez ou autorisez chaque violation. Déplacez les scripts en ligne dans des fichiers ou donnez-leur un nonce, remplacez les gestionnaires
onclicken ligne paraddEventListeneret ajoutez les origines tierces précises que vous utilisez réellement. - Appliquez la politique. Une fois que les rapports se sont tus sur vos parcours clés, remplacez le nom de l'en-tête par
Content-Security-Policy. Vous pouvez continuer à envoyer en parallèle une politique Report-Only plus stricte pour tester l'étape suivante. - Entretenez-la. Chaque nouveau widget, outil de mesure d'audience ou prestataire de paiement impose de modifier la politique. Refaites une vérification après chaque ajout.
Définissez l'en-tête au niveau du serveur, du CDN ou du framework, et non dans une balise <meta> si vous pouvez l'éviter : une politique définie par balise meta ne peut utiliser ni frame-ancestors, ni l'envoi de rapports, ni le mode Report-Only.
Ce que Rudra vérifie dans votre politique
L'outil gratuit de vérification de la sécurité lit la CSP que votre page envoie réellement et affiche, à titre de preuve, les directives analysées. Il signale l'absence de politique, une politique envoyée uniquement en Report-Only, une politique qui ne restreint pas du tout les scripts (ni script-src ni default-src), la présence de 'unsafe-inline' dans la politique des scripts (non signalée lorsqu'un nonce ou un hash est présent), 'unsafe-eval', les sources de scripts trop larges telles que * ou https: (sauf si vous utilisez 'strict-dynamic' avec un nonce ou un hash) et, à titre de notes informatives, l'absence de object-src 'none', l'absence de base-uri et les styles en ligne. Il reconnaît frame-ancestors comme une protection contre le clickjacking lorsque sa valeur n'est ni * ni un simple schéma.
Un outil de vérification peut vous dire ce que votre politique autorise. Il ne peut pas vous dire si vos pages fonctionnent toujours une fois celle-ci en place : seul le test de vos parcours réels en mode report-only le permet.
Questions fréquentes
Une Content-Security-Policy empêche-t-elle les attaques XSS ?
Elle ne corrige pas la faille sous-jacente, mais une politique stricte empêche la plupart des scripts injectés de s'exécuter, ce qui limite les dégâts. Vous devez toujours échapper les sorties et valider les entrées.
'unsafe-inline' dans style-src pose-t-il problème ?
C'est beaucoup moins grave que dans script-src. Des styles injectés peuvent tout de même être détournés dans certaines attaques : le supprimer est donc un bon durcissement, mais traitez d'abord la politique des scripts.
Puis-je utiliser le même nonce sur toutes les pages ?
Non. Un nonce doit être aléatoire et unique pour chaque réponse. Un nonce fixe ou réutilisé peut être copié par un attaquant et n'offre aucune protection.
Combien de temps laisser la politique en Report-Only avant de l'appliquer ?
Assez longtemps pour couvrir vos schémas de trafic réels, soit souvent une à deux semaines, en incluant la connexion, le tunnel de commande et toutes les pages comportant des contenus tiers intégrés. Appliquez la politique lorsque les rapports ne contiennent plus que du bruit, comme celui des extensions de navigateur.