Tous les articles

Construire une CSP solide, étape par étape

CentralCSP Team ·

Dernière mise à jour:

Une politique de sécurité du contenu (CSP) est un en-tête de réponse HTTP qui indique au navigateur quels scripts, styles et autres ressources une page est autorisée à charger et exécuter. Une CSP solide est la défense dans le navigateur la plus efficace dont vous disposez contre le cross-site scripting (XSS) et le vol de données par script. Le plus difficile, ce n'est pas la syntaxe. C'est de construire une politique qui verrouille la page sans la casser.

La manière fiable d'y parvenir, c'est de mesurer en premier et d'appliquer en dernier. Vous commencez par une politique stricte en mode report-only, vous observez ce que le trafic réel aurait enfreint, vous construisez la vraie politique à partir de ces preuves, vous la validez, puis vous activez l'application et continuez à surveiller. Cet article parcourt ce flux de bout en bout, avec l'en-tête de départ exact et la configuration par framework.

Vous débutez avec la CSP ? Commencez par démarrer avec la politique de sécurité du contenu pour les bases, ce qu'est la CSP, votre première politique et votre premier nonce, puis revenez ici pour la construction complète.

Le flux en une minute

Voici l'ensemble du processus avant le détail :

  1. Démarrez en Report-Only. Déployez une politique délibérément stricte sur l'en-tête Content-Security-Policy-Report-Only. Le navigateur ne bloque rien et signale tout ce qu'il aurait bloqué.
  2. Posez l'en-tête sur votre serveur ou votre framework. Utilisez le bon mécanisme pour votre stack afin que l'en-tête atteigne chaque réponse.
  3. Collectez les rapports de violation depuis le trafic réel. Pointez la politique vers un endpoint de reporting et laissez les vrais utilisateurs générer les données.
  4. Construisez la vraie politique à partir de ce que vous voyez. Chaque rapport vous indique une origine ou une ressource inline dont votre page a réellement besoin. Ajoutez le minimum pour l'autoriser.
  5. Validez. Notez la politique, confirmez qu'elle n'a pas de contournement évident et vérifiez qu'elle est vraiment stricte.
  6. Appliquez. Faites passer la politique validée de Content-Security-Policy-Report-Only à Content-Security-Policy.
  7. Continuez à surveiller. Le nouveau code et les tiers changent ce que la page charge. Maintenez le flux de rapports pour détecter les ruptures et les altérations.

Les mêmes sept étapes sous forme de flux :

Le principe derrière chaque étape : n'appliquez jamais une politique que vous n'avez pas mesurée contre le trafic réel. Report-Only est ce qui rend cela sûr.

Étape 1 : commencez par une politique Report-Only stricte

Commencez par une politique volontairement trop stricte. En mode report-only, le navigateur n'applique rien, donc une politique trop serrée ne vous coûte rien d'autre qu'un flux de rapports qui vous disent exactement ce dont la page dépend. C'est la donnée que vous recherchez.

Envoyez ceci sur l'en-tête Content-Security-Policy-Report-Only comme point de départ :

Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' 'report-sample';
  style-src 'self';
  img-src 'self';
  font-src 'self';
  object-src 'none';
  base-uri 'none';
  form-action 'none';
  frame-ancestors 'none';
  frame-src 'self';
  connect-src 'none';
  upgrade-insecure-requests;
  report-uri https://<Endpoint-ID>.report.centralcsp.com;
  report-to csp-endpoint

Ce que fait chaque partie :

  • default-src 'self' est le repli pour toute directive de fetch que vous ne nommez pas, donc tout ce qui n'est pas listé bascule par défaut sur la même origine uniquement.
  • script-src 'self' 'report-sample' autorise les scripts de même origine. 'report-sample' indique au navigateur d'inclure un court échantillon, les 40 premiers caractères, de tout code inline fautif dans le rapport, ce qui rend les violations bien plus faciles à identifier. Il est accepté dans script-src, style-src et leurs variantes -elem et -attr.
  • object-src 'none' bloque les plugins. base-uri 'none' bloque l'injection d'une balise <base>, qui pourrait sinon détourner les URL relatives et déjouer une politique fondée sur les nonces. form-action 'none' bloque les endroits où les formulaires peuvent être soumis. frame-ancestors 'none' est une protection anti-clickjacking et le remplacement moderne de X-Frame-Options ; si vous ne pouvez pas définir de header CSP du tout, voyez la protection contre le clickjacking quand vous ne pouvez pas définir de header CSP. frame-src 'self' limite ce que la page peut encadrer.
  • upgrade-insecure-requests met à niveau les requêtes de sous-ressources non sécurisées vers HTTPS avant qu'elles n'atteignent le réseau, sans repli HTTP. Elle ne prend aucune valeur. Elle ne met pas à niveau les navigations de premier niveau cross-origin et elle ne remplace pas HSTS ; voyez HSTS vs upgrade-insecure-requests pour savoir en quoi les deux diffèrent.
  • report-uri et report-to envoient les rapports de violation à votre collecteur. Plus de détails sur ces deux directives à l'étape 3.

Deux pièges que cette politique est conçue pour révéler

Ce point de départ n'est volontairement pas sûr à appliquer tel quel. Deux directives vont beaucoup remonter de rapports, et c'est précisément le but.

connect-src 'none' bloque chaque requête fetch, XMLHttpRequest, WebSocket et EventSource. Cela cassera presque tout site réel dès que vous l'appliquerez. Démarrer à 'none' force chaque appel réseau effectué par votre page à apparaître dans un rapport, pour que vous puissiez tous les voir. Presque toutes les applications finissent par avoir besoin au moins de connect-src 'self' plus leurs origines d'API et d'analytics.

style-src 'self' remontera des rapports sur les styles inline, que des frameworks comme Angular et les bibliothèques CSS-in-JS injectent constamment. Vous traitez cela avec un nonce de style plutôt qu'en ouvrant la politique avec 'unsafe-inline'.

La politique de départ est un instrument de mesure, pas votre politique finale. Attendez-vous à beaucoup de rapports le premier jour. C'est le flux qui fait son travail.

Étape 2 : posez l'en-tête CSP sur votre serveur ou votre framework

Une politique ne fonctionne que si l'en-tête atteint le navigateur sur chaque réponse, y compris les pages d'erreur. Utilisez le mécanisme conçu pour votre stack. Chaque exemple ci-dessous pose un en-tête CSP en mode application ou report-only ; remplacez le nom de l'en-tête et les directives par la politique de départ de l'étape 1. Pour plus de stacks et des snippets complets, voyez comment définir le header CSP dans chaque framework.

Nginx

Avec le ngx_http_headers_module, utilisez add_header avec always pour que l'en-tête soit envoyé aussi sur les réponses d'erreur :

add_header Content-Security-Policy "default-src 'self'" always;

Apache

Avec mod_headers :

Header always set Content-Security-Policy "default-src 'self'"

Traefik

Avec le middleware headers, posez un en-tête de réponse personnalisé :

traefik.http.middlewares.csp.headers.customresponseheaders.Content-Security-Policy=default-src 'self'

Express (Helmet)

Helmet pose l'en-tête pour vous et prend directement en charge le mode report-only. Les valeurs de directive peuvent être des fonctions, ce qui est la façon de fournir un nonce frais par requête :

app.use(
  helmet({
    contentSecurityPolicy: {
      directives: { "script-src": ["'self'"] },
      reportOnly: true,
    },
  })
);

Django (django-csp)

Ajoutez csp.middleware.CSPMiddleware, puis définissez la politique dans les settings. Utilisez CONTENT_SECURITY_POLICY_REPORT_ONLY pour la phase report-only et CONTENT_SECURITY_POLICY une fois que vous appliquez :

settings.py
CONTENT_SECURITY_POLICY_REPORT_ONLY = {
    "DIRECTIVES": {
        "default-src": ["'self'"],
        "script-src": ["'self'", "'report-sample'"],
    },
}

Next.js (App Router)

Pour une politique stricte fondée sur les nonces avec un rendu dynamique, générez un nonce par requête dans proxy.ts et émettez script-src 'self' 'nonce-...' 'strict-dynamic'. Le mot-clé 'strict-dynamic' est ce qui laisse les scripts de confiance charger d'autres scripts sans allowlist d'hôtes. Pour une politique statique, renvoyez l'en-tête depuis async headers() dans next.config.js. La route proxy est ce qui vous permet de livrer un vrai nonce ; pour la configuration complète, voyez comment configurer un nonce CSP avec Next.js.

Nuxt (nuxt-security)

Configurez security.headers.contentSecurityPolicy dans nuxt.config.ts. Sa valeur par défaut est déjà une politique avec nonce strict plus 'strict-dynamic', donc vous resserrez à partir d'une bonne base plutôt que de partir de zéro.

Laravel (spatie/laravel-csp)

Enregistrez le middleware Spatie\Csp\AddCspHeaders::class, puis définissez votre politique via des presets dans config/csp.php. Le package fournit des presets report-only pour la phase de collecte.

Angular

Angular ne pose pas l'en-tête lui-même ; c'est votre serveur qui le fait avec l'un des mécanismes ci-dessus. Ce qu'Angular vous donne, c'est un nonce par requête, fourni via le token CSP_NONCE ou l'attribut ngCspNonce. Associez-le à un script-src 'self' 'nonce-...' posé côté serveur.

Étape 3 : collectez les rapports de violation depuis le trafic réel

La politique de départ pointe déjà vers un endpoint de reporting. De vrais utilisateurs sur de vraies pages génèrent maintenant les preuves à partir desquelles vous construisez la politique. Les tests synthétiques passent à côté de la longue traîne des scripts tiers, des analytics régionaux et des pages limites, alors collectez depuis le trafic réel.

Envoyez les rapports à votre collecteur

Utilisez la directive report-to avec l'en-tête de réponse Reporting-Endpoints, qui nomme votre collecteur. report-uri est son prédécesseur déprécié ; vous pouvez encore l'envoyer sur la même politique, en pointant vers le même collecteur. Les différences sont dans report-uri vs report-to.

Reporting-Endpoints: csp-endpoint="https://<Endpoint-ID>.report.centralcsp.com"
Content-Security-Policy-Report-Only:
  default-src 'self';
  script-src 'self' 'report-sample';
  ...
  report-uri https://<Endpoint-ID>.report.centralcsp.com;
  report-to csp-endpoint

Lisez les rapports, ne vous y noyez pas

Le JSON brut des rapports CSP arrive une violation à la fois et devient vite des milliers d'enregistrements quasi identiques. Le travail consiste à les regrouper : de quelles origines distinctes et de quelles ressources inline la page a-t-elle réellement besoin, et lesquelles ressemblent à du bruit ou à une tentative d'injection.

C'est la partie pour laquelle CentralCSP est conçu. Il ingère vos rapports de violation Report-Only, les regroupe par directive et par origine, et montre les scripts qui s'exécutent sur chaque page grâce au reporting de hash CSP, pour que vous voyiez exactement quoi autoriser avant d'appliquer. Vous pouvez démarrer un essai gratuit, pointer un en-tête Report-Only vers lui, et regarder les rapports arriver depuis le trafic réel. Si vous préférez d'abord auditer une politique existante, le scanner CSP vérifie ce qu'un site en ligne envoie déjà.

Les violations issues du trafic réel, regroupées par directive et origine bloquée

Étape 4 : construisez la vraie politique à partir de ce que vous voyez

Transformez maintenant les rapports regroupés en une politique. Travaillez directive par directive, et pour chaque rapport décidez l'une de trois choses : la ressource est légitime et vous l'autorisez avec la source la plus étroite possible, c'est quelque chose que vous pouvez supprimer ou héberger vous-même, ou c'est suspect et vous enquêtez.

Quelques règles empiriques au fil de la construction :

  • Ajoutez des origines spécifiques, pas des jokers. Si les rapports montrent des appels connect-src vers votre API et un hôte d'analytics, listez ces deux origines, pas https:.
  • Hébergez vous-même ce que vous pouvez raisonnablement. Moins d'origines tierces signifie une politique plus petite et une surface de chaîne d'approvisionnement plus réduite.
  • Pour les scripts et styles inline dont vous avez vraiment besoin, utilisez un nonce ou un hash, jamais 'unsafe-inline'. Le mot-clé 'report-sample' de la politique de départ et l'inventaire de scripts vous disent quels blocs inline sont les vôtres.

Rendez script-src vraiment solide

C'est l'étape qui sépare une politique moyenne d'une politique solide, et elle s'applique à la politique appliquée, pas au point de départ du premier jour.

Les listes d'autorisation d'hôtes dans script-src sont faibles. Tout endpoint de redirection ouverte ou JSONP sur un hôte autorisé peut devenir un contournement. Le motif solide consiste à abandonner les listes d'hôtes pour les scripts et à faire confiance aux nonces ou aux hashes plus 'strict-dynamic' :

Content-Security-Policy: script-src 'self' 'nonce-r4nd0m' 'strict-dynamic'; object-src 'none'; base-uri 'none'

'strict-dynamic' indique au navigateur d'ignorer les listes d'hôtes, 'self' et 'unsafe-inline' pour les scripts, et de ne faire confiance qu'aux scripts marqués par un nonce ou un hash et à ceux que ces scripts chargent. C'est la façon moderne d'autoriser des scripts sans listes d'hôtes fragiles.

Quoi que vous fassiez, ne vous appuyez pas sur 'unsafe-inline' pour faire disparaître les erreurs de script. Cela réactive exactement l'exécution inline que la CSP existe pour bloquer. Le raisonnement complet est dans pourquoi vous ne devriez jamais utiliser 'unsafe-inline' dans une CSP, et il en va de même pour son cousin : voyez unsafe-eval et comment le supprimer.

Les tag managers tiers sont le cas courant où cela compte. Pour faire tourner GA4 et Google Tag Manager sous un nonce plus 'strict-dynamic' sans lister les hôtes Google dans script-src, voyez CSP avec Google Analytics et Tag Manager.

Pour la liste complète des directives, valeurs et mots-clés au moment d'assembler la politique, voyez la référence des politiques CSP.

Étape 5 : validez la politique

Avant d'appliquer, confirmez que la politique est à la fois stricte et exempte de contournements évidents. Une politique peut être syntaxiquement valide et pourtant être effectivement inutile, par exemple un script-src qui se replie sur 'unsafe-inline' ou autorise un hôte connu pour héberger des scripts arbitraires.

Passez-la dans l'évaluateur CSP pour noter la nouvelle politique et signaler les sources faibles, un object-src manquant, un joker qui anéantit l'intérêt, ou un base-uri manquant. Corrigez ce qu'il fait remonter, puis revérifiez. C'est aussi le moment de confirmer que vous n'avez pas accidentellement élargi une directive en poursuivant les rapports à l'étape 4.

Étape 6 : appliquez

Quand le flux Report-Only est calme, c'est-à-dire que les seules violations restantes sont du bruit ou de vraies tentatives que vous êtes content de bloquer, promouvez la politique. Déplacez exactement les mêmes directives de l'en-tête Content-Security-Policy-Report-Only vers l'en-tête appliqué Content-Security-Policy.

Gardez aussi un report-uri et un report-to sur l'en-tête appliqué. Application et reporting ne s'excluent pas mutuellement : une politique appliquée envoie quand même un rapport pour chaque ressource qu'elle bloque, ce qui est la façon de découvrir quand l'application casse quelque chose que vous aviez manqué.

Un motif courant et plus sûr consiste à faire tourner les deux en-têtes en même temps pendant un moment : la politique validée en application, et une candidate encore plus stricte en Report-Only, pour pouvoir continuer à resserrer sans risque.

Étape 7 : continuez à surveiller

Une CSP n'est pas un en-tête que l'on pose et oublie. Chaque nouvelle fonctionnalité, montée de version d'une dépendance ou balise tierce peut introduire une origine ou un bloc inline que votre politique n'autorise pas, ce qui soit casse la page, soit, pire, signale que quelque chose a changé sur la page à votre insu.

Maintenez le flux de rapports en production. Deux choses à surveiller :

  • Rupture : une nouvelle ressource légitime que votre politique bloque. Le rapport vous dit quoi ajouter.
  • Altération : des scripts ou des origines qui apparaissent sans que personne dans votre équipe les ait introduits. Sur une page de paiement, c'est le signal précoce de formjacking ou d'un skimmer de type Magecart.

La surveillance continue des rapports CSP, l'inventaire de scripts avec détection des CVE, et les alertes sur les scripts nouveaux ou modifiés sont le cœur de la suite CSP de CentralCSP. Pour les pages de paiement en particulier, la surveillance continue des scripts est la façon dont la plateforme vous aide à respecter les exigences côté client de PCI DSS v4 (6.4.3 et 11.6.1) ; voyez CSP pour PCI DSS v4 pour voir comment la politique et les exigences s'alignent. Elle ne certifie pas la conformité ; elle vous donne la surveillance et des preuves exportables, et c'est votre QSA qui valide.

Foire aux questions

Combien de temps faut-il faire tourner une CSP en Report-Only ?

Assez longtemps pour voir le trafic réel sur vos pages et vos tiers, couramment une à deux semaines. Arrêtez quand les seules violations restantes sont du bruit ou de vraies tentatives que vous êtes content de bloquer.

Qu'est-ce qu'une bonne CSP de départ ?

Une politique Report-Only délibérément stricte : default-src 'self', object-src 'none', base-uri 'none', frame-ancestors 'none', et un script-src que vous resserrez vers les nonces. Utilisez l'en-tête de départ ci-dessus et laissez les rapports vous guider.

Faut-il utiliser report-uri ou report-to ?

Utilisez report-to avec l'en-tête Reporting-Endpoints ; c'est le mécanisme actuel. report-uri est le prédécesseur déprécié, que vous pouvez encore inclure sur la même politique si vous le souhaitez.

Qu'est-ce qui rend script-src solide ?

Des nonces ou des hashes plus 'strict-dynamic', à la place des listes d'autorisation d'hôtes. Ainsi, un endpoint de redirection ouverte ou JSONP sur un hôte autorisé ne peut pas devenir un contournement par injection de script.

Un rapide récapitulatif de l'ordre

La force d'une CSP vient de la séquence, pas d'une seule directive :

  • Politique stricte en Report-Only d'abord, pour que la mesure soit gratuite.
  • Collectez depuis le trafic réel, parce que les tests synthétiques passent à côté de la longue traîne.
  • Construisez étroitement, un rapport à la fois, avec des nonces et 'strict-dynamic' pour les scripts.
  • Validez, appliquez et continuez à surveiller, parce que la page ne cesse de changer.

Une note sur la construction de la politique elle-même : un CSP Builder guidé qui transforme vos rapports collectés en une politique prête à livrer arrive, avec son propre guide pratique. En attendant, ce flux vous donne une politique solide à la main.

Pour aller plus loin : le guide de MDN sur la CSP et la spécification W3C CSP Level 3.

Articles liés