Comprendre et implémenter Content-Security-Policy

BenjaminLe 8 mars 2019

Lorsque nous faisons des audits web pour nos clients, nous relevons parfois que certains entêtes de sécurité sont manquants dans les réponses du serveur web : X-Content-Type-Options, X-Frame-Options, X-XSS-Protection... Toutefois, un entête dont nous constatons systématiquement l'absence est Content-Security-Policy. Nous pensons que cet état de fait est dû à la fois à la relative "jeunesse" de cet entête, mais aussi sa complexité de mise en place par rapport aux autres.

Nous allons dans cet article tenter de faire ressortir l'impact très positif que peut avoir cet entête sur la sécurité des sites web, et détailler la manière de la paramétrer afin, nous l'espérons, de faciliter son adoption.

CSP, kézako ?

Content-Security-Policy est un entête de sécurité HTTP dont la première spécification date de février 2015, mise à jour par une deuxième version en décembre 2016, et toujours en cours d'évolution via une troisième version. Concrètement, cet entête renvoyé par le serveur indique aux navigateurs des utilisateurs quelles sources sont autorisées à charger du contenu sur le site concerné.

Prenons un cas simple : vous faites l'inclusion sur votre site de polices d'écriture particulières via un service externe. De plus, vous utilisez un service de mesure d'audience externe. En paramétrant correctement cet entête, vous allez pouvoir indiquer que le navigateur des utilisateurs est autorisé à charger sur votre site des polices d'écriture et des scripts depuis les serveurs de Google (et le vôtre, si nécessaire), mais pas depuis d'autres serveurs. Cela peut donc éviter une intrusion sur un site Internet.

De quelles menaces concrètes cet entête protège-t-il ?

Premier cas : les failles Cross-Site Scripting

Les failles Cross-Site Scripting (ou XSS) proviennent d'un manque de filtrage des saisies utilisateurs. Elles permettent à un attaquant de charger du code Javascript qui s'exécutera dans le navigateur des utilisateurs.

Le code malicieux exécuté peut avoir divers objectifs : entres autres, récupérer toutes les saisies utilisateurs et les envoyer sur le serveur de l'attaquant (nom d'utilisateur, mot de passe, données personnelles, numéro de carte bancaire...) ou miner des cryptomonnaies en utilisant les ressources de l'ordinateur des utilisateurs (cette attaque s'appelle le cryptojacking).

Lorsqu'il découvre une faille XSS, un attaquant va tenter d'infecter un maximum d'utilisateurs. Pour cela, il va écrire du code qui soit sera injecté en une seule fois directement dans le site web vulnérable (on parle de script inline), soit il sera chargé depuis son propre site, donc depuis une adresse IP ou un nom de domaine différent du site web vulnérable.

Dans ce cas, Content-Security-Policy permet d'empêcher un attaquant de charger son script sur la page, qu'il soit inline ou hébergé sur son propre serveur, puisqu'il n'aura pas été explicitement approuvé dans la politique.

Deuxième cas : chargement indélicat de publicités

Il a été observé que certaines entités ont injecté du code Javascript au sein de sites web légitimes, et pourtant non vulnérables (à priori), à fin de servir leurs propres intérêts. Comment ? Ces entités fournissent en fait un accès Internet à leurs clients, et ont pris la liberté de modifier à la volée les sites web consultés.

Il s'agit par exemple du Fournisseur d'Accès Internet américain Comcast, qui injectait du code Javascript de plusieurs centaines de lignes dans les pages consultées par ses visiteurs afin de promouvoir ses équipements type modem et routeurs. Selon la société, il s'agissait d'avertir les utilisateurs qu'ils utilisaient un modem obsolète.

La situation s'est produite sur la connexion WiFi fournie par certains hôtels Marriott à ses clients, ou encore sur la connexion WiFi fournie par AT&T à certains aéroports. Dans les deux cas, la raison invoquée était de trouver des sources de revenus permettant de financer le réseau WiFi aux usagers.

Là encore, Content-Security-Policy permet d'empêcher des sociétés et prestataires de charger leur propre contenu à la volée sur une page, puisqu'il n'aura pas été explicitement approuvé dans la politique.

Troisième cas : librairie externe attaquée

De plus en plus de librairies externes sont hébergées sur des CDN (Content Delivery Networks) et chargées depuis ces serveurs sur les sites qui en font l'usage. On pense notamment à jQuery, Bootstrap ou encore FontAwesome, pour les plus connues. Ces scripts sont donc chargés sur des milliers de sites, qui choisissent d'utiliser ces CDN plutôt que de le servir à leurs utilisateurs depuis leur propre serveur. Cela permet de réduire la bande-passante et d'accélérer les temps de chargement.

Les attaquants y ont vu une occasion incroyable : attaquer un seul serveur, celui qui héberge le code source des librairies, permet de compromettre les milliers de serveurs qui l'utilisent. Cela s'est produit courant 2018, lorsque plusieurs librairies Javascript chargées entre autres par TicketMaster (et près d'un millier de sites de e-commerce) ont été attaquées par le groupe d'attaquants Magecart. Les clients de ces sites e-commerce se sont fait dérober leurs données personnelles et de paiement.

Le chercheur en sécurité Scott Helme a quant à lui constaté le même problème sur de nombreux sites Internet, notamment le site des Cours fédérales américaines, le site de la Sécurité Sociale anglaise, ou encore le site d'un département d'Australie. Tous ces sites chargeaient la librairie BrowseAloud, qui permet aux malvoyants de lire les contenus web, et qui s'est faite attaquer par un groupe d'attaquants. Le but était cette fois de miner des cryptomonnaies sur les ordinateurs des visiteurs.

Dans ce cas particulier, Content-Security-Policy aurait aidé à se protéger, mais n'aurait pas forcément été suffisant. Si l'attaquant utilise la librairie pour charger un fichier se trouvant sur son propre serveur, Content-Security-Policy empêche le code situé sur le serveur de l'attaquant de se charger. En revanche, si l'attaquant compromet la librairie en ajoutant son propre code dans la librairie, Content-Security-Policy utilisé seul est inefficace : il faut alors utiliser Sub-Resource Integrity.

Quatrième cas : extension malicieuse dans le navigateur

Début mars 2020, le journaliste Brian Krebs rapporte qu'un site d'une assurance américaine, Blue Shield of California, était identifié comme malveillant par plusieurs solutions de sécurité du fait qu'il chargeait du contenu malicieux.

Le problème venait d'un employé de la société qui éditait le site web de la Blue Shield of California, qui avait installé l'extension Page Ruler. Cette extension, bien que figurant sur le Chrome Store de Google et pouvant donc paraître saine, avait été vendue quelques temps plus tôt par son développeur à une société de marketing qui en avait modifié le fonctionnement. L'extension s'était alors mise à insérer du code à la fin de ce que les utilisateurs saisissaient sur des plateformes d'édition de contenu, telles que Wordpress ou Joomla, permettant de charger du contenu malveillant depuis des domaines externes.

En mettant en place une Content-Security-Policy, les administrateurs du site auraient pu mitiger cette attaque. En effet, quand bien même le code malveillant aurait été inséré par l'extension dans le code des pages web du site, le serveur aurait indiqué aux navigateurs des visiteurs du site de ne pas charger le script Javascript externe appelé par le code malveillant, puisque le domaine externe n'aurait pas été explicitement autorisé par la CSP.

Comment définir une Content-Security-Policy ?

L’objectif de la première étape est de connaître tous les services externes que vous utilisez : régies publicitaires, outils de mesures d'audiences, librairies Javascript, polices d'écritures, images, services de cartographie, etc. Il convient de lister, pour chacun de ces services, le type de ressource chargée, ainsi que les noms de domaine qui hébergent le contenu chargé.

Par exemple, dans le cas de notre site vitrine (algosecure.fr), cette cartographie des services externes peut se décliner de la manière suivante :

Service Type de ressource Nom de domaine
Mapbox Images, scripts et données de la carte interactive de notre page Contact *.mapbox.com
Notre propre instance de Matomo Scripts et images pour la mesure d'audience nostats.algosecure.fr
YouTube Une iframe, pour afficher une vidéo www.youtube.com
Le site de l'université de Carnegie-Mellon Une image, pour afficher le logo CERT www.sei.cmu.edu

La seconde étape a pour objectif de construire la politique. Il s'agit d'indiquer, pour chaque type de contenu, les sources autorisées. Nous allons également ajouter :

  • une instruction qui permet, pour tous les types non-définis dans notre politique (images, scripts et iframes), d'autoriser le chargement depuis notre propre nom de domaine (c'est à dire 'self'),
  • une instruction qui permet de journaliser les erreurs de chargement.

C'est en effet l'une des fonctionnalités bien pensées de la CSP : si du fait d'un mauvais paramétrage, votre politique venait à bloquer des ressources légitimes dont vous auriez souhaité autoriser le chargement, ces blocages seront journalisés afin de déterminer précisément quelle source a essayé de charger du contenu et quelle instruction de votre politique a été enfreinte, afin de vous aider à améliorer votre CSP. Le site Report-URI permet à cet effet de récolter ces journaux et vous les présenter de manière intelligible. Le service est gratuit pour les "petits" sites, et avec des tarifs progressifs pour les sites plus importants.

Par ailleurs, la CSP possède un mode nommé "Report Only", qui permet dans un premier temps de ne pas bloquer les ressources qui n'auraient pas été autorisées dans la CSP. Seuls les rapports vous seront envoyés, et aucun ressource ne sera bloquée dans la navigateur des clients.

Finalement, notre politique est donc la suivante :

Content-Security-Policy-Report-Only "default-src 'self' nostats.algosecure.fr data: *.mapbox.com www.sei.cmu.edu; frame-src www.youtube.com; report-uri https://algosecure.report-uri.com/r/d/csp/enforce"

Après avoir vérifié que la politique fonctionne correctement et ne bloque pas la navigation sur notre site, nous changerons l'instruction Content-Security-Policy-Report-Only en Content-Security-Policy. Tant que nous laissons le mode Report-Only, rien ne sera bloqué, la navigation ne sera pas impactée côté client.

La dernière étape consiste à d'implémenter cette politique dans le composant du serveur qui sert les pages de votre site à vos utilisateurs.

Implémenter la Content-Security-Policy

Apache

Nous plaçons l'instruction suivante dans le fichier global security.conf (ou dans les paramètres du vhost si l'on possède plusieurs sites hébergés sur le même serveur) :

Header always set Content-Security-Policy-Report-Only "default-src 'self' nostats.algosecure.fr [...]"

Il faut ensuite activer le module headers, puis redémarrer Apache :

sudo a2enmod headers
sudo service apache2 restart

Nginx

L'instruction suivante est à placer dans le contexte serverdu fichier de configuration :

add_header Content-Security-Policy-Report-Only "default-src 'self' nostats.algosecure.fr [...]";

Puis on recharge la configuration :

nginx -s reload

IIS

Par interface graphique, il faut aller dans les paramètres des "En-têtes de réponse HTTP", puis ajouter un entête personnalisé :

Content-Security-Policy sur IIS

Il est également possible d'injecter la section de code suivante dans le contexte customHeaders du contexte httpProtocol de votre fichier web.config :

<add name="Content-Security-Policy-Report-Only" value="default-src 'self' nostats.algosecure.fr [...]" />

Les modifications prennent effet immédiatement.

Tester sa Content-Security-Policy

Une fois la politique implémentée, vous pouvez vous rendre sur le site cspvalidator.org qui vous indiquera si la politique en place est valide.

Content-Security-Policy Validator

Surveiller sa Content-Security-Policy

À ce stade, la politique est implémentée, mais de par l'utilisation du mode Report-Only, rien n'est bloqué côté client. Il faut surveiller que la politique ne crée pas d'interférences avec l'affichage du site, la retravailler pour ajouter ou supprimer des sources, dans l'objectif de passer du mode Report-Only au mode "normal".

Pour cela, le site Report-URI fonctionne bien : il permet d'afficher toutes les ressources qui seraient bloquées côté client. Il convient d'analyser ces blocages, déterminer ceux qui sont légitimes ou non, puis faire évoluer votre Content-Security-Policy en conséquence.

Voici par exemple un rapport d'une ressource ayant été bloquée chez des clients. Ce rapport est en l'occurrence assez simple à comprendre : nous avions inclus une instruction de style au sein du code HTML (donc inline) d'une des pages du site, alors que nous n'avions pas explicitement autorisé la source de contenu inline. Pour résoudre le problème, il nous a suffit de déplacer cette instruction dans un fichier de style CSS (comme ça aurait dû l'être dès le début).

ressource bloquée par un entête Content-Security-Policy

Il faut ainsi passer sur chaque blocage, et évaluer s'il est pertinent ou non d'autoriser le chargement de la ressource sur votre site.

Une fois que vous vous serez assuré que tout fonctionne correctement, vous pourrez supprimer la partie -Report-Only du nom de l'entête configuré dans votre serveur web, puis recharger la configuration. Félicitations, votre Content-Security-Policy est en place et fonctionnelle !

Conclusion

Content-Security-Policy est pour nous l'un des entêtes HTTP les plus puissants en termes de sécurité. Il vous permet de garder la main sur les ressources externes chargées sur votre site. Bien que cela ne constitue pas une protection suffisante pour se prémunir de tous types d'attaques, cet entête offre un niveau de sécurité assez élevé aux sites qui l'implémentent. Son taux d'adoption mérite d'être amélioré. Pour se prémunir encore davantage à l'injection de contenu malveillant sur un site, il est possible d'utiliser Sub-Resource Integrity : ce sera peut-être le sujet d'un prochain article.

Des doutes quant à l'implémentation de cet entête ? N'hésitez pas à nous contacter, nous nous ferons un plaisir de répondre à vos interrogations :)

You've enabled "Do Not Track" in your browser, we respect that choice and don't track your visit on our website.