Standard important du web en matière de sécurité depuis 2010, CSP (Content Security Policy) n’en est pas moins méconnu des techniciens qui “bâtissent le web”. Dans cet article technique, je vous propose de mettre la lumière sur ce standard et sur ses limites.
Travaillant depuis plus de 10 ans dans le domaine du E-Commerce et notamment avec Magento / Adobe Commerce, j’ai voulu également montrer à travers cet article la manière dont CSP est implémenté dans cette solution, vous parler de ses failles et vous proposer des solutions pour tenter d’y remédier.
Qu’est-ce que CSP ?
Définition
“Content Security Policy (abrégé CSP) est un mécanisme de sécurité standardisé permettant de restreindre l’origine du contenu (tel qu’un script Javascript, une feuille de style, etc.) dans une page web à certains sites autorisés. Il permet notamment de mieux se prémunir contre des attaques d’injection de code comme les attaques par cross-site scripting (abrégé XSS) ou par détournement de clic.” – Source
Concrètement, il s’agit à travers un en-tête HTTP Content-Security-Policy, ou une balise <meta>
, d’indiquer au navigateur les scripts autorisés à être exécutés sur votre site web. L’idée étant d’empêcher toute autre exécution, si jamais un script malveillant venait à être injecté sur votre site.
Protections
Correctement configuré, CSP peut vous protéger contre différentes attaques et exploitations de failles de sécurité. L’ensemble des configurations qui vont être évoquées, et bien d’autres, sont largement documentées sur MDN Web Docs.
Les attaques XSS (Cross-Site Scripting)
“Le principe est d’injecter des données arbitraires dans un site web […]. Si ces données arrivent telles quelles dans la page web transmise au navigateur (par les paramètres d’URL, un message posté…) sans avoir été vérifiées, alors il existe une faille : on peut s’en servir pour faire exécuter du code malveillant en langage de script (du JavaScript le plus souvent) par le navigateur web qui consulte cette page.” – Source
Même si elles ne mènent pas systématiquement à des conséquences dramatiques, les XSS restent les attaques privilégiées des hackers au vu des possibilités qu’elles offrent.
La configuration du header CSP minimale pour éviter les attaques XSS est la suivante :
Content-Security-Policy: default-src 'self';
Cela signifie que tous les scripts provenant d’autres domaines que celui du site courant seront bloqués.
Prenons l’exemple du site https://www.dnd.fr/ :
- le script
https://www.dnd.fr/script.js
sera bien exécuté ✅ - le script
https://www.example.org/script.js
sera bloqué ❌
Pour pouvoir exécuter un script provenant d’un domaine de confiance, il est possible de whitelister un ou plusieurs domaines :
Content-Security-Policy: script-src https://www.example.org;
En ce qui concerne les scripts présents directement dans le code de la page (inline), CSP empêche leur exécution par défaut. L’expression unsafe-inline permet de contourner cette sécurité, ce qui n’est évidemment pas recommandé puisque cela enlève presque tout l’intérêt de CSP. Pour pouvoir exécuter un inline script de manière sécurisée, il est possible d’utiliser les principes de hash ou de nonce.
Le Clickjacking
“Le détournement de clic, ou clickjacking, est une technique malveillante visant à pousser un internaute à fournir des informations confidentielles ou à prendre le contrôle de son ordinateur en le poussant à cliquer sur des pages apparemment sûres.” – Source
La directive frame-ancestors permet de se protéger contre le clickjacking en contrôlant les pages embarquées via les éléments HTML <frame>
, <iframe>
, <object>
, <embed>
et <applet>
. Cette directive est la petite sœur de l’en-tête HTTP X-Frame-Options.
Comme pour les autres directives, il est possible d’autoriser le domaine courant et de whitelister d’autres domaines de confiance :
Content-Security-Policy: frame-ancestors 'self' https://www.example.org;
Il est aussi possible de tout bloquer avec :
Content-Security-Policy: frame-ancestors 'none';
Les protocoles non-sécurisés
CSP offre la possibilité de bloquer les protocoles non-sécurisés :
Content-Security-Policy: default-src 'self' https: wss:;
Il est également possible de forcer automatiquement toutes les requêtes HTTP en HTTPS avec la directive upgrade-insecure-requests :
Content-Security-Policy: upgrade-insecure-requests;
Reporting
Par défaut, CSP permet au navigateur de bloquer l’exécution de tous les scripts et éléments qui ne respectent pas les directives utilisées. Ces blocages remontent sous forme d’erreurs dans la console de développement du navigateur.
Pour monitorer ces erreurs, il est possible de configurer un ou plusieurs endpoints externes en utilisant la directive report-to.
Avec cette directive, les erreurs CSP peuvent être envoyées vers n’importe quelle cible. Il existe des solutions spécialisées dans le monitoring de ces données, telles que csper.io ou uriports.com.
Notons que l’en-tête HTTP Content-Security-Policy-Report-Only permet de remonter les violations sans bloquer l’exécution des scripts. À utiliser avec précaution donc.
Les limites de CSP
On l’a vu, sur le papier, tout est là pour assurer une protection complète contre les types d’attaques citées. Cependant, en 2016, trois employés de Google ont publié un article démontrant les limites du mécanisme : “CSP Is Dead, Long Live CSP! On the Insecurity of Whitelists and the Future of Content Security Policy”
Suite à leurs analyses, ils sont arrivés à la conclusion qu’environ 95% des implémentations censées offrir une protection contre les attaques XSS sont en réalité inefficaces.
Cela résulterait d’une part d’une mauvaise utilisation / compréhension de CSP, et d’autre part d’un contournement possible du principe de whitelisting.
Mauvaise utilisation de CSP
L’étude révèle qu’environ 88% des sites qui implémentent CSP utilisent l’expression unsafe-inline
sans spécifier de nonce, ce qui, comme vu plus haut, rend CSP pratiquement inefficace contre les attaques XSS. En d’autres termes, au moment de l’étude, presque tous les sites qui se pensaient protégés étaient en réalité des passoires.
Cela témoigne en partie d’une méconnaissance de l’outil, mais peut aussi s’expliquer par le fait que beaucoup d’applications web embarquent nativement de nombreux inline scripts. Les interdire oblige les intégrateurs à réécrire parfois une grande partie du code, ce qui demande beaucoup d’investissement.
Pour aller plus loin, l’étude a également démontré qu’environ 21% des implémentations ont recours à l’utilisation de wildcards pour autoriser des domaines au sein des directives script-src
ou default-src
, ce qui a pour conséquence d’autoriser les protocoles non-sécurisés, par exemple :
Content-Security-Policy: script-src *.example.org;
Contournement possible des whitelists
Au-delà de tous les problèmes de configuration qui existent, l’équipe de Google a démontré à travers cette étude que la principale raison de l’inefficacité de CSP réside tout simplement dans la possibilité de contourner le principe de whitelisting.
En effet, admettons que l’on souhaite utiliser un script provenant de aaa.com
, lui-même dépendant de scripts provenant de bbb.com
et ccc.com
, il serait alors nécessaire d’autoriser l’ensemble de ces domaines :
Content-Security-Policy: script-src aaa.com bbb.com ccc.com;
De ce fait, si une faille de sécurité est exploitable sur ccc.com
, elle peut potentiellement mener à l’exécution d’un script malveillant autorisé sur notre site.
Whitelister des domaines nous oblige donc à nous assurer que chacun d’entre eux est 100% sécurisé, ce qui est en pratique infaisable sur des applications modernes qui comportent souvent beaucoup de dépendances.
L’étude a d’ailleurs démontré que la grande majorité des 15 domaines les plus whitelistés étaient sujets à des failles inhérentes aux endpoints JSONP et aux librairies AngularJS, rendant totalement inefficace l’utilisation de CSP contre les attaques XSS.
La solution, ou presque…
Introduction de strict-dynamic
Pour répondre à cette problématique de taille, l’équipe de Google a proposé la mise en place d’une nouvelle expression : strict-dynamic
, permettant d’autoriser les scripts en cascade de manière sécurisée à l’aide d’un nonce :
Content-Security-Policy: script-src 'strict-dynamic' 'nonce-R4nd0m';
Cela signifie que pour un script déclaré avec ce nonce, l’ensemble des dépendances de ce script seront autorisées automatiquement sous le même nonce :
<script nonce="R4nd0m" src="https://www.example.org/script.js">
Le nonce étant une chaîne de caractères aléatoire et renouvelée à chaque chargement de page, il est impossible pour les hackers de le deviner. Cette façon de procéder améliore significativement la sécurité de CSP et facilite grandement sa mise en place puisqu’elle ne nécessite plus de whitelister chaque domaine.
Du coup, on est sauvés ?
Pas tout à fait… En 2017, dans une seconde étude, une autre équipe de Google a démontré que dans les applications modernes, la plupart de ces protections peuvent être contournées par le biais de ce qu’ils ont appelé les script gadgets.
Les script gadgets sont de petits fragments de code JavaScript présents dans des librairies légitimes, pouvant être détournés à des fins malicieuses grâce au DOM.
Il s’agit donc pour les hackers d’utiliser ce code légitime, en apparence inoffensif, pour injecter des scripts malicieux et ainsi passer sous les radars de CSP.
Dans cet exemple simpliste, l’attribut data-text
est injecté dans le contenu de la div
faisant office de bouton via un script légitime. Si l’on parvient à détourner le contenu de cet attribut, alors on peut injecter ce que l’on désire, ici un script, dans le contenu de la page. Ce script sera exécuté au chargement de la page, peu importe la configuration CSP appliquée.
D’après l’étude, ces script gadgets seraient présents dans la majorité des librairies JS modernes :
- trending JavaScript frameworks (Vue.js, Aurelia, Polymer)
- widely popular frameworks (AngularJS, React, EmberJS)
- older still popular frameworks (Backbone, Knockout, Ractive, Dojo)
- libraries and compilers (Bootstrap, Closure, RequireJS)
- jQuery-based libraries (jQuery, jQuery UI, jQuery Mobile)
Même si on peut admettre que cette liste de 2017 n’est plus forcément à jour, le problème reste entier, et les applications modernes sont de plus en plus difficiles à sécuriser tant elles dépendent de librairies tierces.
Donc même si CSP a le mérite d’exister, et que cela peut offrir une protection contre certaines attaques moins sophistiquées, considérer aujourd’hui que nous sommes entièrement protégés contre toute attaque XSS grâce à CSP est un mythe.
Et Magento dans tout ça ?
CSP dans Adobe Commerce (Magento)
L’implémentation de CSP dans Adobe Commerce (Magento) a été introduite à partir de la version 2.3.5, sortie en avril 2020. L’ensemble des directives, le whitelisting, le mode report, etc. peuvent être gérés et configurés à travers le module Magento_Csp.
Magecart attacks
Leader du marché des solutions E-Commerce, Adobe Commerce (Magento) est une solution populaire. Si populaire que l’un des types d’attaque XSS les plus connus se nomme Magecart, contraction de “Magento” et de “Shopping Cart” (panier d’achat).
Aussi appelée Web Skimming, cette attaque consiste à injecter un script qui se déclenche au niveau du tunnel d’achat pour en extraire les informations de paiement, et notamment de carte bancaire.
Différentes techniques sont utilisées par les hackers pour y arriver, comme par exemple, prendre possession des projets Github abandonnés pour en détourner le code source. Les applications qui utilisent ces projets en dépendance se retrouvent par conséquent infectées, de manière indirecte, sans avoir été elles-mêmes piratées.
Mais la finalité est toujours la même : un script malicieux est injecté dans le code du site ciblé, et on l’a vu, malgré ses défauts, CSP peut offrir une protection contre cela.
Encore des limites…
Malheureusement, l’implémentation de CSP dans Adobe Commerce (Magento) comporte, elle aussi, des limites…
Inline Scripts
Avant Adobe Commerce (Magento) 2.4, l’expression unsafe-inline
était imposée sans pouvoir spécifier de nonce ou de hash, ce qui pour rappel rend CSP quasiment inefficace.
À partir de la version 2.4, une solution a été apportée avec l’introduction de la classe SecureHtmlRenderer qui permet l’implémentation du système de hash pour les inline scripts.
<?php
$scriptContent = <<<script
//<![CDATA[
console.log('I am a whitelisted script');
//]]>
script;
?>
<?= $secureRenderer->renderTag('script', [], $scriptContent, false); ?>
Depuis la version 2.4.7, pour permettre la mise en conformité PCI DSS 4.0, le support du nonce a enfin été introduit avec la classe CspNonceProvider.
Il est important de noter qu’une récente mise à jour de sécurité backport ces ajouts sur les versions 2.4.4-p9, 2.4.5-p8 et 2.4.6-p6 (ainsi que les versions antérieures pour les clients membres du Extended Support Program).
Cependant, malgré ces améliorations, l’expression unsafe-inline
est toujours activée par défaut, hormis pour les scripts du tunnel d’achat. Autrement dit, nativement, seul le tunnel d’achat offre une protection renforcée contre les inline scripts.
Il est néanmoins possible de désactiver l’expression unsafe-inline
globalement via un développement spécifique, en surchargeant la configuration par défaut dans le fichier etc/config.xml du module de votre choix :
<default>
<csp>
<policies>
<storefront>
<scripts>
<inline>0</inline>
</scripts>
</storefront>
</policies>
</csp>
</default>
Il faudra dès lors utiliser le SecureHtmlRenderer
sur tous vos scripts inline customs.
À noter que cela va générer un nonce qui sera mis en cache sur chaque page par le Full Page Cache. Nous avons créé une issue pour exposer la problématique, mais d’après la core team cela ne compromettrait pas la sécurité de l’application.
Unsafe Eval
À cause de sa structure et de ses dépendances, Adobe Commerce (Magento) impose l’expression unsafe-eval pour l’ensemble des scripts, et permet donc la création de lignes de code à la volée depuis des chaînes de caractères.
“There is no way to disable unsafe-eval right now since we use it for UI components and some of the front-end libraries we employ need it (like jQuery). A strategy must be created to remove eval() usage from UI components.”- Source
Utiliser cette expression revient à dire : “n’importe qui est autorisé à exécuter du code arbitraire dans mon application à partir d’un point d’entrée”. Il s’agit donc d’une faille de sécurité importante pour laquelle il n’existe pas de solution à ce jour.
Strict dynamic
On l’a vu ensemble, il a été démontré que le whitelisting compromet l’efficacité de CSP contre les attaques XSS, et l’expression strict-dynamic
a été introduite pour pallier cela.
Sachez qu’Adobe Commerce (Magento) utilise toujours une approche de whitelisting, et que sa structure actuelle ne permet pas l’implémentation de strict-dynamic
.
Mettre en place cette expression nécessiterait de refondre entièrement le thème Luma natif, ce qui n’est malheureusement pas une option à ce jour…
Conclusion
On l’a vu donc, pour que CSP soit viable, il faut que sa configuration soit irréprochable :
- bannir l’utilisation de toutes les expressions
unsafe-*
- utiliser les principes de nonce et de hash
- utiliser l’expression
strict-dynamic
au profit des whitelists
Et même avec tout cela, l’efficacité de CSP est remise en cause avec l’existence des scripts gadgets dans de nombreuses librairies tierces…
Quant à Adobe Commerce (Magento), en matière de configuration CSP, la solution fait pâle figure :
- activation par défaut de l’expression
unsafe-inline
(hors tunnel d’achat) - utilisation imposée de l’expression
unsafe-eval
- utilisation imposée des whitelists
Cela est si problématique que certains membres de la communauté recommandent sans détour la désactivation du module Magento-CSP
.
Si l’on est d’accord pour dire que CSP est une véritable passoire tel qu’implémenté aujourd’hui dans Adobe Commerce (Magento), cela peut néanmoins rester efficace contre les attaques grossières, le clickjacking, les protocoles non-sécurisés, l’injection de scripts basiques, etc.
J’adopterais donc une autre approche : il vaut mieux avoir un vigile qui attrape un voleur sur dix que de laisser passer tous les voleurs. En revanche, utiliser CSP dans Adobe Commerce (Magento) comme seule protection contre les attaques XSS est une erreur à ne pas faire.
La responsabilité revient alors à chaque développeur, chaque marchand, de prendre conscience de cela et de mettre tout en œuvre pour améliorer la sécurité de leurs applications. Et des possibilités, heureusement, il y en a !
Recommandations pour Adobe Commerce (Magento)
Pour vous aider dans cette démarche, voici une liste de bonnes pratiques à adopter dès maintenant.
✅ Mettez à jour votre application
On l’a vu, Adobe Commerce (Magento) 2.4.7 comporte son lot d’améliorations CSP * :
- Support du système de nonce pour les inline scripts
- Désactivation de l’expression
unsafe-inline
dans le tunnel d’achat - Activation par défaut du mode restrict dans le tunnel d’achat
Sans pour autant régler totalement les limites évoquées, cette dernière version apporte néanmoins de nouvelles solutions pour renforcer la sécurité de votre plateforme, et notamment du tunnel d’achat. Nous vous recommandons donc de mettre à jour votre application et de vous tenir informés des dernières releases.
* backporté sur les autres versions par le dernier patch de sécurité (11/06/24).
✅ Désactivez le mode report-only
Par défaut dans Adobe Commerce (Magento), CSP est configuré en mode report-only (hors tunnel d’achat), ce qui force l’en-tête HTTP Content-Security-Policy-Report-Only, qui pour rappel permet de remonter les violations sans bloquer l’exécution des scripts.
Ce header peut être utilisé à des fins de développement, mais en aucun cas en production, car il n’offre aucune protection contre les attaques.
Configurez donc le mode restrict comme le suggère la documentation.
✅ Désactivez l’expression unsafe-inline globalement
Comme indiqué dans la partie Inline Scripts, si cela est possible sur votre site (en fonction des features que vous utilisez), désactivez l’expression unsafe-inline
globalement pour les scripts et utilisez systématiquement le SecureHtmlRenderer
durant les développements.
✅ Sécurisez les requêtes HTTP
Il existe une directive CSP upgrade-insecure-requests qui permet de forcer toutes les URLs en HTTPS.
Adobe Commerce (Magento) dispose d’une configuration permettant d’activer cette directive :
bin/magento config:set -lc web/secure/enable_upgrade_insecure 1
✅ Évitez le whitelisting de domaines
Lors des développements, évitez au maximum d’ajouter des dépendances à des ressources externes.
Si vous êtes contraints de whitelister un ou plusieurs domaines, évitez l’utilisation de wildcards.
✅ Contrôlez votre header CSP
Prenez le temps de vérifier la conformité de votre header CSP.
Il existe plusieurs sites pour cela :
✅ Monitorez votre site
Comme abordé dans la partie Reporting, monitorez les violations CSP avec un outil spécialisé. En fonction de vos besoins, ces solutions sont relativement bon marché et vous permettront d’y voir plus clair, d’obtenir des recommandations et d’identifier rapidement les attaques.
Les URLs de reporting peuvent être configurées très facilement comme l’indique la documentation.
✅ Respectez les bonnes pratiques de développement
Cela peut paraître évident, mais une mauvaise manipulation des données au sein de votre code peut avoir des conséquences dramatiques, notamment en ouvrant la porte aux injections SQL, qui sont très souvent à l’origine d’injections de scripts malveillants.
Une attention toute particulière doit être portée à la manipulation de requêtes SQL en PHP, surtout lorsque celles-ci comportent des données dynamiques.
L’ensemble de ces bonnes pratiques sont détaillées dans la documentation.
Vous pouvez automatiser l’identification des requêtes SQL “en dur” grâce au Magento Coding Standard :
vendor/bin/phpcs --standard=Magento2 --sniffs=Magento2.SQL.RawQuery app/code/ app/design/
✅ Sécurisez le rendu des valeurs dynamiques
Le principe d’Output Processing consiste à traiter et sécuriser le rendu de toute chaîne de caractères pouvant provenir de sources externes.
Pour les templates PHTML par exemple, il s’agit d’utiliser systématiquement la classe Escaper
durant les développements.
La règle est simple : Do not trust dynamic values (Méfiez-vous des valeurs dynamiques).
Vous pouvez automatiser l’identification des valeurs dynamiques non-sécurisées grâce au Magento Coding Standard :
vendor/bin/phpcs --standard=Magento2 --sniffs=agento2.Security.XssTemplate app/code/ app/design/
✅ Sécurisez les formulaires
Les attaques CSRF (Cross-Site Request Forgery) consistent à envoyer des données à un controller depuis un formulaire hébergé sur un site malveillant.
Il est facile de s’en protéger via l’implémentation de form keys, en héritant des interfaces natives Http<Method>ActionInterface.
✅ Activez le Security Scan
Le Security Scan d’Adobe Commerce (Magento) intègre l’outil de détection de malwares de Sansec, il est donc essentiel de l’activer pour obtenir des rapports quotidiens. Il peut être configuré simplement en suivant la documentation.
Les recommandations du Security Scan sont également incluses dans le SWAT.
✅ Utilisez un WAF
Le WAF (Web Application Firewall) est un outil qui permet de filtrer et surveiller le trafic HTTP de votre site. Il apporte une sécurité supplémentaire contre les XSS, les injections SQL, et d’autres vulnérabilités. Il est donc important de doter votre infrastructure d’un WAF.
À noter que si vous disposez d’une infrastructure Adobe Commerce Cloud, le WAF est directement intégré à Fastly. C’est aussi le cas plus généralement si vous utilisez Fastly comme système FPC.
✅ Testez votre application
Enfin, le meilleur moyen de vérifier si votre site est sécurisé est sans doute de le pirater vous-même. Il existe pour cela un outil open source qui vous permettra de lancer toutes sortes de tests d’intrusion automatisés, et ce même si vous n’avez pas de connaissances particulières en la matière : ZAP.
L’avenir de CSP sur Adobe Commerce (Magento)
Même si depuis la sortie du module Magento_CSP
un certain nombre d’ajouts ont été faits pour renforcer la sécurité de l’application, on peut difficilement espérer de nouvelles améliorations majeures de la configuration CSP, dont les lacunes dépendent essentiellement de la structure du thème natif d’Adobe Commerce (Magento).
Du côté du thème Hyvä, qui devient presque un incontournable, l’existence d’une version d’AlpineJS CSP-friendly ouvre la porte à de nombreuses améliorations CSP, notamment en ce qui concerne la suppression de l’expression unsafe-eval
.
Techniquement, une nouvelle version du thème Hyvä implémentant cette version d’AlpineJS pourrait voir le jour, mais cela nécessiterait là encore une importante refonte du thème, incluant de nombreux breaking changes et la mise à jour de l’ensemble des modules de compatibilité. Un travail titanesque donc, que l’équipe Hyvä considère, mais qui n’est malheureusement pas prévu pour le moment.
À suivre…
Liens utiles
- Content Security Policy – MDN web docs
- Defending against XSS with CSP – Auth0
- Limiter les scripts intersites (XSS) avec une CSP (Content Security Policy) stricte – web.dev
- XSS in practice: how to exploit XSS in web applications (Walktrought into Google XSS game) — StackZero
- Magecart : An overview and defense mechanisms – Trustwave
- Content Security Policy (CSP) : safe usage of unsafe-eval? – Stack overflow
- Content Security Policy (CSP) Bypass – HackTricks
- Validity of a WAF as a Compensating/Alternative Control for CSP, X-XSS-Protection, etc – Stack Exchange