Tous les articles

Débuter avec la Content Security Policy

CentralCSP Team ·

Dernière mise à jour:

Une politique de sécurité du contenu (Content Security Policy, CSP) est un header de réponse HTTP qui indique au navigateur quels scripts, styles, images et autres ressources votre page est autorisée à charger et à exécuter. Vous envoyez un header, le navigateur le lit à chaque chargement de page, et tout ce qui ne figure pas sur votre liste est bloqué. Ce blocage est ce qui arrête la plupart des attaques de type cross-site scripting (XSS) et d'injection de script.

Voici l'introduction en douceur. À la fin, vous saurez ce qu'est une CSP, comment la livrer, comment écrire une première politique simple, comment autoriser un script inline avec un nonce, et comment tester tout cela sans casser votre site. Pour le workflow de production complet et chaque framework, ce billet renvoie vers les guides plus détaillés plutôt que de les répéter.

Ce qu'est une CSP, en une minute

Voyez une politique comme une liste d'invités que le navigateur vérifie avant d'exécuter quoi que ce soit. Vous remettez la liste au navigateur sous forme de header de réponse. Quand la page charge un script ou une feuille de style, le navigateur consulte la liste. Présent sur la liste, il l'exécute. Absent de la liste, il refuse et (si vous le demandez) le signale.

Pourquoi c'est important : la plupart des attaques XSS fonctionnent en amenant votre page à exécuter un script injecté par l'attaquant. Une bonne CSP dit « n'exécute que les scripts que j'ai explicitement autorisés », donc le script injecté n'est pas sur la liste et ne s'exécute jamais. C'est une seconde ligne de défense derrière la validation des entrées, la couche qui limite les dégâts quand quelque chose passe à travers.

Comment livrer une CSP

Vous livrez une CSP via le header de réponse HTTP Content-Security-Policy, défini sur chaque réponse que votre serveur envoie. C'est la méthode recommandée et celle que ce guide utilise tout du long.

Il existe une seconde option, une balise <meta http-equiv="Content-Security-Policy" content="..."> dans votre HTML. Elle fonctionne pour les cas simples mais reste limitée :

  • Elle ne peut pas utiliser frame-ancestors, sandbox, ni les directives de reporting (report-uri et report-to).
  • Elle ne peut pas du tout livrer une politique Report-Only.
  • Elle ne protège rien de ce qui se charge avant la balise, donc un script injecté en haut de la page n'est pas protégé.

À cause de ces lacunes, préférez le header HTTP. Le reste de ce billet suppose le header.

Votre première politique, en Report-Only

Démarrez toujours une CSP en Report-Only. Avec le header Content-Security-Policy-Report-Only, le navigateur ne bloque rien et signale seulement ce que la politique aurait bloqué, ce qui vous permet de déployer une politique stricte sur du trafic réel sans risquer une page cassée. Vous passerez au header d'application plus tard, une fois que les reports seront calmes.

Voici un exemple complet de CSP pour démarrer. Chaque type de ressource est fixé sur votre propre origine ('self') ou désactivé ('none'), et la politique envoie ses reports de violation vers un endpoint CentralCSP :

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

Ce que fait chaque partie :

  • default-src 'self' est le repli pour tout ce que vous ne nommez pas. Les lignes 'self' (script-src, style-src, img-src, font-src, connect-src, frame-src, form-action) maintiennent chaque type de ressource sur votre propre origine.
  • Les lignes 'none' désactivent ce dont la plupart des sites n'ont jamais besoin : object-src 'none' bloque les anciens plugins comme <object> et <embed>, base-uri 'none' empêche une balise <base> injectée de réécrire vos URL relatives, et frame-ancestors 'none' empêche que votre page soit mise dans un cadre, ce qui constitue une protection contre le clickjacking.
  • report-uri et report-to envoient chaque violation vers votre endpoint CentralCSP, nommé dans le header Reporting-Endpoints. C'est là que vous observez ce que la politique bloquerait. Ce sont deux mécanismes différents ; voyez report-uri vs report-to pour choisir lequel utiliser, et comment configurer la Reporting API du navigateur pour câbler l'endpoint.

Remplacez <Endpoint-ID> par votre propre endpoint de reporting CentralCSP. À mesure que les reports arrivent, vous assouplissez les lignes trop strictes (un CDN dont votre img-src a besoin, un hôte d'analytics pour connect-src) et vous gardez le reste verrouillé, jusqu'à ce que la politique convienne à votre site. Pour un exemple concret courant, voyez comment faire tourner Google Analytics et Tag Manager sous une CSP stricte.

Définir le header sur votre serveur

Une politique ne protège les utilisateurs que si le header atteint le navigateur. Vous le définissez là où votre stack ajoute les headers de réponse, en utilisant la politique Report-Only complète ci-dessus comme valeur. Voici deux exemples courants ; la configuration complète par framework couvre Apache, Traefik, Django, Next.js, Nuxt, Laravel et Angular.

Nginx

Utilisez add_header avec always pour que le header soit envoyé aussi sur les réponses d'erreur :

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

Express (Helmet)

Helmet définit le header pour vous à partir d'un objet de directives :

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

Votre premier nonce

Tôt ou tard, vous aurez un script inline que vous ne pourrez pas déplacer dans un fichier, par exemple un petit bloc de configuration rendu côté serveur. Une politique stricte bloque les scripts inline par défaut, ce qui est exactement ce que vous voulez pour la sécurité. Un nonce est le moyen de laisser passer un script inline précis sans ouvrir la porte à tous les autres.

Un nonce est une valeur aléatoire fraîche que votre serveur génère à chaque réponse. Vous la placez dans le header et vous la répétez sur le script inline. Le navigateur n'exécute que les scripts inline dont l'attribut nonce correspond à la valeur du header. Un attaquant qui injecte un script ne peut pas deviner la valeur, donc le script injecté reste bloqué.

Générez-le à partir d'une source aléatoire cryptographiquement sûre, 16 octets (128 bits) est la recommandation courante, encodés en base64. N'utilisez jamais Math.random() ; c'est prévisible. En Node :

const crypto = require("crypto");
const nonce = crypto.randomBytes(16).toString("base64");

Placez ensuite ce nonce dans la politique et sur le script :

Content-Security-Policy: script-src 'nonce-r4nd0mBase64Value'; object-src 'none'; base-uri 'none'
<script nonce="r4nd0mBase64Value"> <!-- [!code word:r4nd0mBase64Value] -->
  // votre script inline de confiance
</script>

Deux règles maintiennent un nonce sûr. Il doit être unique par réponse, donc générez-en un nouveau à chaque fois, ne réutilisez jamais un nonce d'une requête à l'autre. Et il doit provenir d'une source aléatoire sûre, jamais Math.random().

Préférez les nonces (ou les hashes) au mot-clé 'unsafe-inline'. Ajouter 'unsafe-inline' à une directive de script réactive tous les scripts inline, ce qui jette à la poubelle la protection pour laquelle vous avez mis en place la CSP. Le raisonnement complet se trouve dans pourquoi vous ne devriez jamais utiliser unsafe-inline en CSP.

Générer un nonce frais à chaque requête et le faire passer dans vos templates dépend du framework. Comment mettre en place un nonce couvre les stacks courantes, et si vous êtes sur Next.js, comment configurer un nonce CSP avec Next.js détaille de bout en bout la configuration de l'App Router.

De Report-Only à l'application

Vous avez démarré en Report-Only, donc rien n'a encore été bloqué. Le navigateur s'est contenté de signaler ce que votre politique bloquerait. Lisez ces reports, autorisez les origines dont votre site a réellement besoin, supprimez le reste, et regardez les violations diminuer.

Quand les reports sont calmes, c'est-à-dire que les seules entrées restantes sont du bruit ou des tentatives que vous êtes content de bloquer, faites passer la politique en production. Déplacez les mêmes directives du header Content-Security-Policy-Report-Only vers le header d'application Content-Security-Policy. Rien d'autre ne change, seulement le nom du header.

Le reporting ne s'arrête pas quand vous appliquez la politique. Une politique en application envoie toujours un report pour tout ce qu'elle bloque, vous gardez donc la même visibilité et détectez ce qui casse, ou toute altération, sur vos pages.

Déboguer les violations dans Chrome DevTools

Quand quelque chose est bloqué, le navigateur vous le dit. Ouvrez Chrome DevTools et vous verrez les violations CSP à deux endroits (pour la démonstration complète, voyez déboguer les violations CSP dans DevTools) :

  • La Console affiche un message du style « Refused to load... » qui nomme la ressource et la directive qui l'a bloquée.
  • Le panneau Issues détaille la violation : la directive enfreinte, la ressource bloquée, l'emplacement source, et un lien vers l'élément qui l'a provoquée.

Ouvrez le panneau Issues depuis le bouton Issues de la barre d'action de DevTools, ou depuis More tools > Issues. Le panneau Issues est généralement plus rapide à lire car il regroupe les violations et renvoie directement vers l'élément fautif.

C'est parfait pour vérifier ponctuellement une page sur votre propre machine, et l'extension de navigateur CentralCSP vous donne la même vue par page pendant que vous naviguez. Ni l'un ni l'autre ne vous montre ce que rencontrent les vrais utilisateurs sur toutes vos pages et chez les tiers. Pour cela, vous collectez les reports Report-Only depuis le trafic réel, ce qui est l'étape suivante.

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

Où aller ensuite

Vous avez maintenant les bases : un header, une première politique, un nonce et une façon sûre de tester. Le saut entre ce point et une CSP de niveau production consiste à collecter les violations depuis le trafic réel, à construire la politique à partir de ces preuves, à la valider, à l'appliquer et à la surveiller dans le temps. Ce workflow complet se trouve dans comment construire une CSP solide, étape par étape.

Quelques outils et fonctionnalités CentralCSP vous aident en chemin :

  • Le scanner CSP vérifie quel header un site en ligne envoie déjà.
  • L'évaluateur CSP note une politique et signale les sources faibles ou les directives manquantes.
  • La suite CSP de CentralCSP collecte vos reports Report-Only depuis le trafic réel, les regroupe et montre les scripts qui s'exécutent sur chaque page, pour que vous voyiez exactement quoi autoriser avant d'appliquer.

Quand vous êtes prêt à voir les reports arriver, démarrez un essai gratuit et pointez un header Report-Only vers lui.

Questions fréquentes

Comment débuter avec la CSP ?

Démarrez sur le header Content-Security-Policy-Report-Only avec une politique qui fixe chaque type de ressource sur 'self' ou 'none' et qui pointe les reports vers un collecteur. Corrigez ce que les reports montrent, puis déplacez la même politique vers le header d'application Content-Security-Policy.

Quelle est la CSP la plus simple pour commencer ?

Une politique qui fixe chaque type de ressource sur 'self' ou 'none', livrée sur le header Content-Security-Policy-Report-Only avec les reports pointés vers votre endpoint CentralCSP. Commencez là pour que rien ne casse, puis ajustez-la à partir des reports. La politique de départ ci-dessus est un bon modèle, et il existe un modèle de CSP de départ plus complet à copier.

Comment ajouter une CSP sans casser mon site ?

Livrez-la d'abord sur le header Content-Security-Policy-Report-Only. Le navigateur ne bloque rien et signale seulement ce qu'il aurait bloqué, ce qui vous permet de corriger chaque problème avant de passer au header d'application.

Comment autoriser un script inline sous CSP ?

Donnez-lui un nonce. Générez une valeur aléatoire fraîche par réponse, placez-la dans script-src sous la forme 'nonce-...', et répétez-la dans l'attribut nonce du script. Évitez 'unsafe-inline', qui autorise tous les scripts inline.

Faut-il utiliser une balise meta ou un header HTTP pour la CSP ?

Utilisez le header HTTP. Une balise meta ne peut pas utiliser frame-ancestors, ne peut pas livrer Report-Only, et ne protège pas le contenu qui se charge avant la balise. Voir Balise meta CSP ou header HTTP pour la comparaison complète.

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

Articles liés