Content Security Policy

Openweb.eu.org > Articles  > Content Security Policy

Abstract

De nombreuses initiatives tendent à amener plus de sécurité sur les sites Internet. La généralisation de HTTPS avec des initiatives comme Let’s Encrypt, la restriction de l’utilisation de certaines API avec HTTPS, de nombreux outils permettant de tester et d’améliorer la sécurité des sites, etc.

Parmi ce vaste effort de sécurisation des sites, une technologie offre de nouvelles possibilités surprenantes sur le front-end. Son petit nom est « Content Security Policy ».

Article

Introduction

Actuellement, quand un navigateur reçoit des contenus (images, CSS, JavaScript, etc.), son travail est de rendre la page le plus vite et le mieux possible. Hormis quelques mécanismes de sécurité qui seront abordés ici-même dans un futur proche, à aucun moment, ce dernier ne se pose ce genre de questions :

  • Est-ce que les concepteurs du site considèrent tel élément comme légitime ?
  • Dois-je bien exécuter ou prendre en compte tel élément ?

Ajoutons à cela que le front-end est souvent considéré comme un milieu extrêmement hostile :

  • il est de bon usage dans le back-end de ne jamais faire confiance à ce qui peut venir du front-end (formulaire, etc.) ;
  • l’agent utilisateur a toujours le dernier mot vis-à-vis d’un site qu’il consulte (styles utilisateurs, etc.), les concepteurs d’un site n’ont que peu de garanties de ce qu’il sera fait des pages qu’ils envoient ;
  • et une fois la page envoyée chez l’agent utilisateur, on ne contrôle pas nécessairement ce qu’il peut s’y passer [1].

Et pourtant, il existe bien des moyens de sécuriser certains aspects du front-end, et notamment via une technologie nommée CSP.

Des directives de sécurité front-end

CSP signifie « Content Security Policy », en bon français « Politique de Sécurité des Contenus ». L’idée est simple : en envoyant un en-tête HTTP, des directives de sécurité sont données au navigateur, et ce dernier les appliquera à la lettre. En clair, ces directives vont dire au navigateur ce qu’il est autorisé à exécuter… ou non.

Historiquement, CSP a été pensé pour réduire la surface d’attaque sur le front-end vis-à-vis des attaques dites de Cross-Site-Scripting (en anglais : mitigate XSS). En pratique, CSP permet d’aller beaucoup plus loin et offre de nombreux outils.

Fonctionnement et possibilités

Principes

Le principe est très simple : un en-tête HTTP est envoyé au navigateur contenant des directives de sécurité front-end. En voici un exemple via PHP :

header("Content-Security-Policy: <ici vos directives>");

Voici quelques exemples de directives :

default-src 'self' ;

default-src sera la directive appliquée si aucune directive n’est définie pour un élément. Le mot-clé 'self' indique que tout ce qui sera sur le même port, même protocole et même nom de domaine sera autorisé.

script-src 'self' www.piwik.com ;

Dans cet exemple, la directive script-src indique donc 'self', et également que tous les fichiers JavaScript provenant de www.piwik.com seront autorisés à s’exécuter.

img-src 'self' data: ;

Cette directive img-src indique également 'self', le mot-clé data: autorise quant à lui les contenus dit embarqués (via data-uri).

Liste des directives

CSP niveau 1 permet de spécifier des directives pour les éléments suivants :

  • script-src : les sources autorisées pour les scripts JS
  • styles-src : les sources autorisées pour les styles CSS
  • img-src : les sources autorisées pour les images
  • connect-src : s’applique pour XMLHttpRequest (AJAX), WebSocket ou EventSource
  • font-src : les sources pour les web fonts
  • object-src : les sources des plug-ins (par exemple : <object>, <embed>, <applet>)
  • media-src : les sources pour les balises <audio> et <video>
  • etc.

CSP niveau 2 ajoute de nouvelles directives :

  • child-src : les sources valides pour les web workers et les éléments comme <frame> et <iframe> (remplace frame-src déprécié de CSP niveau 1)
  • form-action : les sources autorisées utilisées dans l’attribut action d’un formulaire
  • frame-ancestors : les sources autorisées à embarquer la ressource dans <frame>, <iframe>, <object>, <embed> ou <applet>
  • upgrade-insecure-requests : indique à l’agent utilisateur de réécrire l’URL en changeant HTTP en HTTPS (pour les sites avec beaucoup d’anciennes URLs qui ont besoin d’être réécrites).
  • etc.

Pour une meilleur rétro-compatibilité avec les directives dépréciées, il suffit de copier le contenu d’une directive dans la version dépréciée. Par exemple, il est possible de copier le contenu de child-src et de le coller dans frame-src.

Exemple

Voici un exemple présenté lors de la conférence « Codeurs en Seine » de cette année, cette page contient des éléments indésirables. Sans CSP, voici le résultat :

exemple de page sans CSP (page défacée)

Pas terrible. Envoyons des directives CSP au navigateur, voici un résumé des directives complètes.

content-security-policy:
  default-src 'none' ;
  script-src 'self' www.google-analytics.com ;
  style-src 'self' ;
  img-src 'self' www.google-analytics.com data: ;
  font-src 'self';
  frame-ancestors 'none' ;

Que va-t-il se passer ? Le navigateur les reçoit et va les appliquer à la lettre selon la règle suivante : tout ce qui n’est expressément autorisé dans les directives CSP sera interdit. Par interdit, comprenez : bloqué, non exécuté, non affiché.

Point important : par défaut, le JavaScript en ligne n’est pas autorisé. Autrement dit, les balises script, attributs onclick, etc. dans le HTML ne seront pas autorisés. Cela peut se faire avec script-src 'unsafe-inline' et via script-src 'unsafe-eval', toutefois, c’est déconseillé (n’oublions pas que le but de CSP est de limiter les failles XSS).

Et il en va de même pour la fonction eval de JavaScript, ainsi que pour les styles en ligne. Autrement dit, ces directives qui semblaient être gentilles sont en fait plutôt strictes et autorisent le minimum nécessaire.

Le navigateur reçoit donc des éléments, les fichiers JavaScript/CSS sur le même nom de domaine/port/protocole seront donc autorisés, et un script dont la provenance n’a pas été autorisée sera bloqué. Idem pour le JavaScript et les CSS en ligne.

Le navigateur va indiquer dans la console les éléments bloqués et le pourquoi de ce blocage, exemple :

Notifications CSP dans la console du navigateur

Et voici le résultat de la même page protégée par CSP :

exemple de la même page avec CSP (page normale)

Mieux, n’est-ce pas ?

Note : CSP ne « désinfectera » pas vos pages, il évitera juste l’exécution des contenus non autorisés.

Déployer CSP

Un premier conseil : ne foncez pas tête baissée pour déployer CSP sur un site en production en vous disant que cela sera facile. C’est probablement la meilleure façon si vous souhaitez… vous dégoûter de CSP. Ne faites pas ainsi sur un site en production, vous avez de grandes chances d’y laisser des plumes, vous êtes prévenus !

Comme vu précédemment, vous êtes entre des directives très précises et le navigateur, qui les appliquera de manière stricte et disciplinée. Le point faible sera donc très souvent… vous ! Il est très facile d’oublier une source de contenu, qu’un script a besoin de JavaScript en ligne pour fonctionner, etc. Et il n’est clairement pas dans la nature de CSP d’être indulgent.

Selon votre situation, cela peut être plus ou moins aisé, néanmoins il convient de prendre des précautions. Heureusement, plusieurs outils de CSP permettent de s’y préparer sans risque.

Report-URI

Les notifications envoyées dans la console du navigateur peuvent être envoyées sur une adresse, et donc traitées. Cela s’indique dans les directives via :

report-uri /csp-parser.php ;

Sur cette adresse, le navigateur enverra les notifications au format JSON. Voici un exemple volontairement minimaliste de script en PHP pouvant le traiter :

$data = file_get_contents('php://input');
if ($data = json_decode($data, true)) {
  $data = json_encode(
        $data,
        JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES
  );
  mail(EMAIL, SUBJECT, $data);
}

En clair :

  • le script reçoit les données ;
  • il vérifie que le format est bien du JSON ;
  • il aplatit les informations au format JSON en texte…
  • …et envoie par mail ce texte.

Ce genre de script est parfait pour un développement avec peu de fréquentation, où CSP est pris en compte depuis le début.

Important : attention sur une production beaucoup plus visitée ! Pour prendre un exemple, si une page génère 100 erreurs CSP, et que cette page est visitée 100 fois par jour, rien que cette page vous enverra 10 000 notifications par jour ! Pensez à filtrer sur votre adresse de report [2], ou le cas échéant à utiliser un service dédié à cette tâche, comme Report-URI.

Report-only

Comme vu précédemment, un des soucis de CSP est son côté binaire : on l’active… ou non. Une possibilité vient résoudre ce problème : Report-only.

Comme son nom l’indique, on va indiquer au navigateur de seulement rapporter les erreurs générées par les directives spécifiées, sans bloquer quoi que ce soit côté navigateur.

header("Content-Security-Policy-Report-Only: <vos directives>");

Cela permet de tester des directives sans danger : soit en cas de premier déploiement de directives CSP, soit dans le cadre d’un renforcement de vos directives. Si par exemple des directives fonctionnent en production et que l’on souhaite les rendre plus strictes, on peut garder les directives et tester les directives plus dures sans risquer de bloquer quoi que ce soit.

En résumé, avec Report-URI et Report-only, vous pouvez tester des directives sans danger, et monitorer tout ce qu’il se passe d’un point de vue CSP.

Dans notre exemple donné plus haut, il est donc tout à fait possible d’être prévenu de suite que quelque chose d’anormal se passe sur la page.

Hashes et Nonces

Souvent le problème qui se pose vient des scripts en ligne : l’idéal est de ne pas les autoriser, mais les aléas d’un projet peuvent le nécessiter. C’est là qu’entrent en scène les hashes et les nonces. Même si ces deux possibilités sont amenées par CSP niveau 2 (et donc pour le moment pas supportées partout), en voici une présentation rapide.

Hashes

Sans autoriser tous les scripts en ligne, il est possible d’en autoriser certains, via un contrôle sur leur intégrité (via une « fonction de hachage cryptographique », que nous appellerons un hash pour simplifier). En pratique, cela se précise dans les directives :

script-src 'sha256-d08VMHuG2SHhy9Tk5IX7cA6bYafas7GiX/Fo9/hzDsY=' ;

Ce calcul d’intégrité correspond à ce script : <script>alert('bonjour le monde.');</script>. Il s’obtient ainsi : (exemple en PHP)

echo base64_encode(hash('sha256', "alert('bonjour le monde.');", true));
// donne : d08VMHuG2SHhy9Tk5IX7cA6bYafas7GiX/Fo9/hzDsY=

Attention : le moindre espace change le résultat du calcul !

Nonces

Nonce signifie : Number used Once (« numéro utilisé une seule fois »). Encore une fois, cela se définit dans les directives :

script-src 'nonce-12345666789' ; 

Et cela s’utilisera ainsi :

<script nonce="12345666789">…</script>

Cela revient à dire au navigateur de ne rien bloquer pour ce qui correspond à ce nonce.

Attention : comme son nom l’indique, un nonce doit être unique, re-généré à chaque fois, et bien entendu doit être non trivial et non devinable !

Des spécifications plutôt bien stabilisées

Un point important à mentionner : CSP n’est pas une technologie qui fonctionne uniquement sur certains navigateurs en version alpha avec 5 flags à activer. CSP niveau 1 et 2 sont des Candidate Recommendations au W3c, les travaux sur la standardisation de CSP sont donc raisonnablement avancés.

Côté support, le support du niveau 1 est excellent, comme le confirme le site « Can I Use » :

Support de CSP niveau 1 selon Can I Use

Le niveau 2 est plus récent, au moment de l’écriture de cet article, le support en est donc moins optimal :

Support de CSP niveau 2 selon Can I Use

Quoi qu’il en soit, le niveau 1 est tout à fait utilisable en production. Le seul défaut de CSP consiste en de petits défauts d’implémentation chez les navigateurs : les soucis sont souvent des faux-positifs dans les notifications. On peut aussi noter que CSP est parfois trop efficace : par exemple les bookmarklets peuvent être bloqués par les directives CSP d’un site, alors qu’ils ne devraient pas l’être. Les divers navigateurs travaillent activement à régler ces soucis.

Pourquoi utiliser CSP ?

Il ne vous aura pas échappé que de nombreuses initiatives incitent à amener plus de sécurité en standard sur les sites internet. CSP en est un maillon particulièrement intéressant, ce n’est pas un hasard si des outils comme Mozilla Observatory, Dareboost, Hardenize, Security headers, etc. testent précisément son utilisation et vous incitent à le déployer.

La priorité : CSP est là avant tout pour la sécurité des utilisateurs. CSP aide à limiter les dégâts potentiels des failles de type XSS, et même plus largement sur les contenus que l’on peut qualifier d’indésirables. Un point important : toute la mécanique de CSP qui a été décrite ici… est totalement transparente pour l’utilisateur. Vous naviguez probablement quotidiennement sur des sites qui utilisent CSP (Twitter, Github, Facebook, etc.), et vous ne vous en êtes probablement pas rendu compte avant de lire cette phrase.

Ensuite, CSP est un outil extrêmement puissant pour les concepteurs/gestionnaires de sites, c’est à ce titre qu’on le qualifie souvent « de couteau suisse du front-end » [3]. De par la nature de CSP, pour faire fonctionner quelque chose sur votre front-end, vous devez savoir ce dont cela a besoin comme sources de contenus et comment cela va fonctionner (styles/JavaScript en ligne, fonction eval, etc.) [4].

De facto, l’activation de CSP vous invite à respecter de bonnes pratiques :

  • éviter les fonctions posées à la va-vite en inline, qui seront déportées vers des fichiers externes ;
  • connaître le fonctionnement intrinsèque d’un script ou d’un plugin ;
  • maîtriser vos sources de contenus et pouvoir poser une politique stricte en la matière ;
  • etc.

Si vous appréciez le concept de l’orthogonalité abordé dans L’orthogonalité en CSS, avec CSP, vous avez trouvé le meilleur allié pour le respecter !

Ce principe est d’ailleurs évoqué en tant que bonne pratique Opquast : les scripts manipulent des classes plutôt que les styles en ligne.

On peut même parler d’éco-conception : on ne s’autorise que le nécessaire, ce qui n’est pas un mal en soi. C’est extrêmement bien illustré par exemple dans Firefox si vous faites Maj + F2 et si vous tapez security CSP, on vous encourage à réduire la surface d’attaque autant que possible sur vos directives : (exemple sur Twitter)

Affichage des directives CSP de Twitter sous Firefox

De plus, constatant que le poids moyen des pages ne fait qu’augmenter ces dernières années, s’appliquer un peu de frugalité ne fera de mal à personne !

Le futur de CSP

En l’état actuel des choses, le niveau 1 est tout à fait utilisable en production. Le niveau 2 peut être plus délicat à déployer, du fait du support moins large de ses possibilités.

Les travaux ont commencé sur le niveau 3, qui devrait amener de nouvelles directives. Toutefois, comme ce niveau est actuellement à l’état de brouillon de travail, il n’est pas nécessaire d’aller plus avant. Si le sujet vous intéresse, vous pouvez consulter des ressources sur le sujet en bas de cet article.

Conclusion

CSP est un outil absolument fantastique pour le front-end qui permet :

  • de limiter les effets d’attaques XSS et de contenus indésirables ;
  • de servir de trousse à outils pour des opérations importantes (migration HTTPS, etc.) ;
  • d’organiser une maîtrise globale de votre front-end tout en servant de garde-fou pour des bonnes pratiques ;
  • mais en plus de surveiller et de monitorer ce qu’il se passe sur le front-end.

Cette technologie a tout pour retenir votre attention, et vaut la peine d’être essayée et déployée à grande échelle.

Ressources, compléments

Notes

[4Et cela va parfois vous surprendre lors de l’utilisation de scripts tiers…

À propos de cet article

  • Openweb.eu.org
  • Profil : Expert, Gourou
  • Thème : Sécurité
  • Auteur :
  • Publié le :
  • Mise à jour : 6 décembre 2016

Répondre à cet article

Qui êtes-vous ?
Ajoutez votre commentaire ici
  • Ce formulaire accepte les raccourcis SPIP [->url] {{gras}} {italique} <quote> <code> et le code HTML <q> <del> <ins>. Pour créer des paragraphes, laissez simplement des lignes vides.

Suivre les commentaires : RSS 2.0 | Atom