Les scripts intersites (XSS), qui permettent d'injecter des scripts malveillants dans une application Web, constituent l'une des plus grandes failles de sécurité Web depuis plus de dix ans.
Content Security Policy (CSP) est une couche de sécurité supplémentaire qui permet d'atténuer les attaques XSS. Pour configurer une CSP, ajoutez l'en-tête HTTP Content-Security-Policy
à une page Web et définissez des valeurs qui contrôlent les ressources que l'user-agent peut charger pour cette page.
Cette page explique comment utiliser un CSP basé sur des nonces ou des hachages pour atténuer le trafic XSS, plutôt que les CSP couramment utilisés basés sur une liste d'autorisation de l'hôte qui laissent souvent la page exposée au XSS, car ils peuvent être contournés dans la plupart des configurations.
Terme clé: un nonce est un nombre aléatoire utilisé une seule fois pour marquer une balise <script>
comme fiable.
Terme clé: une fonction de hachage est une fonction mathématique qui convertit une valeur d'entrée en une valeur numérique compressée appelée hachage. Vous pouvez utiliser un hachage (par exemple, SHA-256) pour marquer une balise <script>
intégrée comme fiable.
Une stratégie de sécurité du contenu basée sur des nonces ou des hachages est souvent appelée CSP stricte. Lorsqu'une application utilise une CSP stricte, les pirates informatiques qui détectent des failles d'injection HTML ne peuvent généralement pas les utiliser pour forcer le navigateur à exécuter des scripts malveillants dans un document vulnérable. En effet, une CSP stricte autorise uniquement les scripts hachés ou les scripts avec la valeur nonce correcte générée sur le serveur. Les pirates informatiques ne peuvent donc pas exécuter le script sans connaître le nonce correct pour une réponse donnée.
Pourquoi utiliser une CSP stricte ?
Si votre site dispose déjà d'une CSP ressemblant à script-src www.googleapis.com
, elle n'est probablement pas efficace contre les attaques intersites. Ce type de CSP est appelé CSP par liste d'autorisation. Elles nécessitent beaucoup de personnalisation et peuvent être contournées par les pirates informatiques.
Les CSP strictes basées sur des nonces ou hachages cryptographiques évitent ces pièges.
Structure CSP stricte
Une Content Security Policy de base utilise l'un des en-têtes de réponse HTTP suivants:
CSP stricte basée sur les non-ce
Content-Security-Policy:
script-src 'nonce-{RANDOM}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
CSP stricte basée sur le hachage
Content-Security-Policy:
script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic';
object-src 'none';
base-uri 'none';
Les propriétés suivantes rendent une CSP comme celle-ci "stricte" et donc sécurisée:
- Il utilise des nonces
'nonce-{RANDOM}'
ou des hachages'sha256-{HASHED_INLINE_SCRIPT}'
pour indiquer les balises<script>
approuvées par le développeur du site qui doivent s'exécuter dans le navigateur de l'utilisateur. - Il définit
'strict-dynamic'
pour réduire les efforts liés au déploiement d'une CSP basée sur un nonce ou un hachage, en autorisant automatiquement l'exécution des scripts créés par un script de confiance. Cela permet également de débloquer l'utilisation de la plupart des bibliothèques et des widgets JavaScript tiers. - Il n'est pas basé sur les listes d'autorisation d'URL. Il ne subit donc pas de contournements courants des CSP.
- Elle bloque les scripts intégrés non approuvés tels que les gestionnaires d'événements intégrés ou les URI
javascript:
. - Il limite
object-src
à la désactivation des plug-ins dangereux tels que Flash. - Elle limite
base-uri
pour bloquer l'injection de balises<base>
. Cela empêche les pirates informatiques de modifier l'emplacement des scripts chargés à partir d'URL relatives.
Adopter une CSP stricte
Pour adopter une CSP stricte, vous devez:
- Déterminez si votre application doit définir une CSP basée sur un nonce ou un hachage.
- Copiez la CSP à partir de la section Structure CSP stricte et définissez-la comme en-tête de réponse dans votre application.
- Refactorisez les modèles HTML et le code côté client pour supprimer les formats incompatibles avec CSP.
- Déployez votre CSP.
Tout au long de ce processus, vous pouvez utiliser l'audit des bonnes pratiques Lighthouse (à partir de la version 7.3.0 avec l'indicateur --preset=experimental
) pour vérifier si votre site dispose d'une CSP et s'il est suffisamment strict pour contrer les failles XSS.
Étape 1: Déterminez si vous avez besoin d'une CSP basée sur un nonce ou un hachage
Voici comment fonctionnent les deux types de CSP stricte:
CSP basée sur Nonce
Avec une CSP basée sur des nonces, vous générez un nombre aléatoire au moment de l'exécution, l'incluez dans votre CSP et l'associez à chaque tag de script de votre page. Un pirate informatique ne peut pas inclure ni exécuter un script malveillant sur votre page, car il doit deviner le nombre aléatoire correct pour ce script. Cela ne fonctionne que si le nombre n'est pas devinable et s'il est généré lors de l'exécution pour chaque réponse.
Utilisez une CSP basée sur des nonces pour les pages HTML affichées sur le serveur. Pour ces pages, vous pouvez créer un nombre aléatoire pour chaque réponse.
CSP basée sur le hachage
Pour une CSP basée sur le hachage, le hachage de chaque tag de script intégré est ajouté à CSP. Chaque script a un hachage différent. Un pirate informatique ne peut pas inclure ni exécuter un script malveillant sur votre page, car le hachage de ce script doit figurer dans votre CSP pour s'exécuter.
Utilisez une CSP basée sur le hachage pour les pages HTML diffusées de manière statique ou les pages qui doivent être mises en cache. Par exemple, vous pouvez utiliser une CSP basée sur le hachage pour les applications Web monopages créées à l'aide de frameworks tels qu'Angular, React ou d'autres, qui sont diffusées de manière statique sans rendu côté serveur.
Étape 2: Définissez une CSP stricte et préparez vos scripts
Lorsque vous définissez une CSP, plusieurs options s'offrent à vous:
- Mode Rapport uniquement (
Content-Security-Policy-Report-Only
) ou Application forcée (Content-Security-Policy
). En mode Rapport uniquement, le CSP ne bloque pas encore les ressources. Par conséquent, rien sur votre site ne plante, mais vous pouvez voir les erreurs et générer des rapports sur tout ce qui aurait été bloqué. En local, lorsque vous configurez votre CSP, cela n'a pas vraiment d'importance, car les deux modes affichent les erreurs dans la console du navigateur. Le mode application forcée peut vous aider à trouver des ressources bloquées par votre brouillon de CSP, car le blocage d'une ressource peut rendre votre page défaillante. Le mode Rapport uniquement devient particulièrement utile plus tard dans le processus (voir l'étape 5). - En-tête ou balise HTML
<meta>
. Pour le développement local, il peut être plus pratique d'utiliser une balise<meta>
pour ajuster votre CSP et voir rapidement son impact sur votre site. Toutefois :- Par la suite, lors du déploiement de votre CSP en production, nous vous recommandons de le définir en tant qu'en-tête HTTP.
- Si vous souhaitez définir votre CSP en mode Rapport uniquement, vous devez le définir en tant qu'en-tête, car les balises Meta CSP ne sont pas compatibles avec le mode Rapport uniquement.
Définissez l'en-tête de réponse HTTP Content-Security-Policy
suivant dans votre application:
Content-Security-Policy: script-src 'nonce-{RANDOM}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Générer un nonce pour CSP
Un nonce est un nombre aléatoire utilisé une seule fois par chargement de page. Une CSP basée sur des nonces ne peut atténuer le XSS que si les pirates informatiques ne peuvent pas deviner la valeur du nonce. Un nonce CSP doit être:
- Une valeur aléatoire forte de manière cryptographique (idéalement, une longueur de 128 bits ou plus)
- Nouvelle génération pour chaque réponse
- Encodage en base64
Voici quelques exemples d'ajout d'un nonce CSP dans des frameworks côté serveur:
- Django (python)
- Express (JavaScript):
const app = express(); app.get('/', function(request, response) { // Generate a new random nonce value for every response. const nonce = crypto.randomBytes(16).toString("base64"); // Set the strict nonce-based CSP response header const csp = `script-src 'nonce-${nonce}' 'strict-dynamic'; object-src 'none'; base-uri 'none';`; response.set("Content-Security-Policy", csp); // Every <script> tag in your application should set the `nonce` attribute to this value. response.render(template, { nonce: nonce }); });
Ajouter un attribut nonce
aux éléments <script>
Avec une CSP basée sur des nonces, chaque élément <script>
doit comporter un attribut nonce
qui correspond à la valeur de nonce aléatoire spécifiée dans l'en-tête CSP. Tous les scripts peuvent avoir le même nonce. La première étape consiste à ajouter ces attributs à tous les scripts afin que la CSP les autorise.
Définissez l'en-tête de réponse HTTP Content-Security-Policy
suivant dans votre application:
Content-Security-Policy: script-src 'sha256-{HASHED_INLINE_SCRIPT}' 'strict-dynamic'; object-src 'none'; base-uri 'none';
Pour plusieurs scripts intégrés, la syntaxe est la suivante : 'sha256-{HASHED_INLINE_SCRIPT_1}' 'sha256-{HASHED_INLINE_SCRIPT_2}'
.
Charger dynamiquement des scripts sources
Étant donné que le hachage CSP n'est compatible avec les navigateurs que pour les scripts intégrés, vous devez charger tous les scripts tiers de manière dynamique à l'aide d'un script intégré. Le hachage des scripts provenant de sources n'est pas compatible avec tous les navigateurs.
<script> var scripts = [ 'https://example.org/foo.js', 'https://example.org/bar.js']; scripts.forEach(function(scriptUrl) { var s = document.createElement('script'); s.src = scriptUrl; s.async = false; // to preserve execution order document.head.appendChild(s); }); </script>
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
Remarques concernant le chargement des scripts
L'exemple de script intégré ajoute s.async = false
pour garantir que foo
s'exécute avant bar
, même si bar
est chargé en premier. Dans cet extrait de code, s.async = false
ne bloque pas l'analyseur pendant le chargement des scripts, car ils sont ajoutés de manière dynamique. L'analyseur ne s'arrête que pendant l'exécution des scripts, comme pour les scripts async
. Toutefois, avec cet extrait, gardez à l'esprit les points suivants:
-
L'un ou les deux scripts peuvent s'exécuter avant la fin du téléchargement du document. Si vous souhaitez que le document soit prêt au moment de l'exécution des scripts, attendez l'événement
DOMContentLoaded
avant d'ajouter les scripts. Si cela pose problème parce que le téléchargement des scripts ne commence pas assez tôt, utilisez des tags de préchargement plus tôt sur la page. -
defer = true
ne fait rien. Si nécessaire, exécutez le script manuellement si nécessaire.
Étape 3: Refactoriser les modèles HTML et le code côté client
Vous pouvez exécuter des scripts à l'aide de gestionnaires d'événements intégrés (tels que onclick="…"
et onerror="…"
) et d'URI JavaScript (<a href="javascript:…">
). Cela signifie qu'un pirate informatique qui trouve un bug XSS peut injecter ce type de code HTML et exécuter du code JavaScript malveillant. Une CSP basée sur un nonce ou un hachage interdit l'utilisation de ce type de balisage.
Si votre site utilise l'un de ces formats, vous devrez les refactoriser afin de les rendre plus sûrs.
Si vous avez activé CSP à l'étape précédente, vous pouvez afficher les cas de non-respect de CSP dans la console chaque fois que CSP bloque un modèle incompatible.
Dans la plupart des cas, la solution est simple:
Refactoriser les gestionnaires d'événements intégrés
<span id="things">A thing.</span> <script nonce="${nonce}"> document.getElementById('things').addEventListener('click', doThings); </script>
<span onclick="doThings();">A thing.</span>
Refactoriser les URI javascript:
<a id="foo">foo</a> <script nonce="${nonce}"> document.getElementById('foo').addEventListener('click', linkClicked); </script>
<a href="javascript:linkClicked()">foo</a>
Supprimer eval()
de votre JavaScript
Si votre application utilise eval()
pour convertir les sérialisations de chaînes JSON en objets JS, vous devez refactoriser ces instances en JSON.parse()
, ce qui est également plus rapide.
Si vous ne pouvez pas supprimer toutes les utilisations de eval()
, vous pouvez toujours définir une CSP stricte basée sur un nonce, mais vous devez utiliser le mot clé CSP 'unsafe-eval'
, ce qui rend votre stratégie légèrement moins sécurisée.
Vous trouverez ces exemples et d'autres d'exemples d'une telle refactorisation dans cet atelier de programmation CSP strict:
Étape 4 (facultative): Ajoutez des créations de remplacement pour assurer la compatibilité avec les anciennes versions du navigateur
Si vous devez utiliser d'anciennes versions de navigateur:
- L'utilisation de
strict-dynamic
nécessite l'ajout dehttps:
comme solution de secours pour les versions antérieures de Safari. Si vous effectuez cette opération :- Tous les navigateurs compatibles avec
strict-dynamic
ignorent la création de remplacementhttps:
. Par conséquent, la force de la règle n'est pas réduite. - Dans les anciens navigateurs, les scripts externes ne peuvent se charger que s'ils proviennent d'une origine HTTPS. Cette méthode est moins sécurisée qu'une CSP stricte, mais elle empêche tout de même certaines causes courantes de XSS, telles que les injections d'URI
javascript:
.
- Tous les navigateurs compatibles avec
- Pour assurer la compatibilité avec les versions de navigateur très anciennes (4 ans et plus), vous pouvez ajouter
unsafe-inline
en remplacement. Tous les navigateurs récents ignorentunsafe-inline
si un nonce ou un hachage CSP est présent.
Content-Security-Policy:
script-src 'nonce-{random}' 'strict-dynamic' https: 'unsafe-inline';
object-src 'none';
base-uri 'none';
Étape 5: Déployer votre CSP
Après avoir vérifié que votre CSP ne bloque aucun script légitime dans votre environnement de développement local, vous pouvez la déployer en préproduction, puis dans votre environnement de production:
- (Facultatif) Déployez votre CSP en mode rapport uniquement à l'aide de l'en-tête
Content-Security-Policy-Report-Only
. Le mode Rapport uniquement est pratique pour tester une modification potentiellement destructive comme une nouvelle CSP en production avant de commencer à appliquer les restrictions CSP. En mode Rapport uniquement, votre CSP n'affecte pas le comportement de votre application. Toutefois, le navigateur génère tout de même des erreurs et des rapports de non-respect de la console lorsqu'il rencontre des modèles incompatibles avec votre CSP. Vous pouvez ainsi identifier les problèmes pour vos utilisateurs finaux. Pour en savoir plus, consultez la page API Reporting. - Lorsque vous êtes sûr que votre CSP n'affectera pas votre site pour vos utilisateurs finaux, déployez-la à l'aide de l'en-tête de réponse
Content-Security-Policy
. Nous vous recommandons de définir votre CSP à l'aide d'un en-tête HTTP côté serveur, car il est plus sécurisé qu'un tag<meta>
. Une fois cette étape terminée, votre CSP commence à protéger votre application contre les attaques XSS.
Limites
Une CSP stricte fournit généralement une couche de sécurité supplémentaire renforcée qui permet de limiter les attaques XSS. Dans la plupart des cas, CSP réduit considérablement la surface d'attaque en rejetant les modèles dangereux tels que les URI javascript:
. Toutefois, selon le type de CSP que vous utilisez (nonces, hachages, avec ou sans 'strict-dynamic'
), CSP ne protège pas non plus votre application dans certains cas:
- Si vous noncez un script, mais qu'une injection est effectuée directement dans le corps ou dans le paramètre
src
de cet élément<script>
. - En cas d'injections dans les emplacements des scripts créés dynamiquement (
document.createElement('script')
), y compris dans toutes les fonctions de bibliothèque qui créent des nœuds DOMscript
en fonction des valeurs de leurs arguments. Cela inclut certaines API courantes telles que.html()
de jQuery, ainsi que.get()
et.post()
dans jQuery < 3.0. - Si des injections de modèles sont disponibles dans d'anciennes applications AngularJS Un pirate informatique pouvant injecter dans un modèle AngularJS peut l'utiliser pour exécuter du code JavaScript arbitraire.
- Si la règle contient
'unsafe-eval'
, des injections danseval()
,setTimeout()
et quelques autres API rarement utilisées.
Les développeurs et les ingénieurs en sécurité doivent accorder une attention particulière à ces modèles lors des revues de code et des audits de sécurité. Pour en savoir plus à ce sujet, consultez Content Security Policy: un gâchis réussi entre renforcement et atténuation.
Complément d'informations
- CSP Is Dead, Long Live CSP! sur l'insécurité des listes blanches et l'avenir du règlement sur la sécurité du contenu
- Évaluateur CSP
- Conférence LocoMoco : Règle de sécurité du contenu : un bon compromis entre renforcement et atténuation
- Conférence Google I/O: Sécuriser les applications Web grâce aux fonctionnalités modernes de la plate-forme