Browser Support
Le script intersites (XSS), qui permet d'injecter des scripts malveillants dans une application Web, est l'une des plus grandes failles de sécurité Web depuis plus de dix ans.
La Content Security Policy (CSP) est une couche de sécurité supplémentaire qui permet de limiter 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 une CSP basée sur des nonces ou des hachages pour limiter les attaques XSS, au lieu des CSP basées sur une liste d'autorisation d'hôtes couramment utilisées, qui exposent souvent la page aux attaques XSS, car elles peuvent être contournées dans la plupart des configurations.
Terme clé : un nonce est un nombre aléatoire utilisé une seule fois que vous pouvez utiliser 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 Content Security Policy 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 trouvent 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 n'autorise que 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 la valeur nonce correcte pour une réponse donnée.
Pourquoi utiliser une CSP stricte ?
Si votre site dispose déjà d'une CSP semblable à script-src www.googleapis.com, elle n'est probablement pas efficace contre les attaques de script intersites. Ce type de CSP est appelé CSP de 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 des hachages cryptographiques évitent ces écueils.
Structure CSP stricte
Une Content Security Policy stricte de base utilise l'un des en-têtes de réponse HTTP suivants :
CSP stricte basée sur un nonce
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 :
- Elle utilise des nonces
'nonce-{RANDOM}'ou des hachages'sha256-{HASHED_INLINE_SCRIPT}'pour indiquer les balises<script>que le développeur du site considère comme fiables pour l'exécution dans le navigateur de l'utilisateur. - Il définit
'strict-dynamic'pour réduire l'effort de 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 débloque également l'utilisation de la plupart des bibliothèques et widgets JavaScript tiers. - Elle n'est pas basée sur des listes d'autorisation d'URL. Elle ne souffre donc pas des contournements courants de 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-srcpour désactiver les plug-ins dangereux comme Flash. - Elle limite
base-uripour 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 un CSP basé sur un nonce ou un hachage.
- Copiez la CSP de la section Structure de CSP stricte et définissez-la comme en-tête de réponse dans l'ensemble de votre application.
- Refactorisez les modèles HTML et le code côté client pour supprimer les modèles incompatibles avec la CSP.
- Déployez votre CSP.
Vous pouvez utiliser l'audit des bonnes pratiques de Lighthouse (v7.3.0 et versions ultérieures avec l'option --preset=experimental) tout au long de ce processus pour vérifier si votre site dispose d'une CSP et si elle est suffisamment stricte pour être efficace contre les attaques 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 strictes :
CSP basée sur un nonce
Avec une CSP basée sur un nonce, vous générez un nombre aléatoire au moment de l'exécution, vous l'incluez dans votre CSP et vous l'associez à chaque tag de script de votre page. Un pirate informatique ne peut pas inclure ni exécuter de script malveillant sur votre page, car il devrait deviner le nombre aléatoire correct pour ce script. Cela ne fonctionne que si le nombre n'est pas prévisible et qu'il est généré à l'exécution pour chaque réponse.
Utilisez une CSP basée sur un nonce 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 un hachage, le hachage de chaque tag de script intégré est ajouté à la CSP. Chaque script possède un hachage différent. Un pirate informatique ne peut pas inclure ni exécuter de script malveillant sur votre page, car le hachage de ce script doit se trouver dans votre CSP pour qu'il s'exécute.
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 un hachage pour les applications Web monopages créées avec des 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 mode d'application (Content-Security-Policy). En mode rapport uniquement, la CSP ne bloque pas encore les ressources, de sorte que rien ne se casse sur votre site, mais vous pouvez voir les erreurs et obtenir des rapports pour tout ce qui aurait été bloqué. En local, lorsque vous définissez votre CSP, cela n'a pas vraiment d'importance, car les deux modes affichent les erreurs dans la console du navigateur. Le mode d'application peut vous aider à trouver les ressources que votre brouillon de CSP bloque, car le blocage d'une ressource peut donner l'impression que votre page est défectueuse. Le mode "Rapport uniquement" devient plus utile plus tard dans le processus (voir l'étape 5). - En-tête ou balise HTML
<meta>. Pour le développement local, une balise<meta>peut être plus pratique pour ajuster votre CSP et voir rapidement comment elle affecte votre site. Toutefois :- Plus tard, lorsque vous déploierez 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 la 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 la CSP
Un nonce est un nombre aléatoire utilisé une seule fois par chargement de page. Une CSP basée sur un nonce ne peut atténuer les attaques XSS que si les pirates ne peuvent pas deviner la valeur du nonce. Un nonce CSP doit être :
- Une valeur aléatoire cryptographiquement forte (idéalement d'une longueur de 128 bits ou plus)
- Généré à nouveau 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 un nonce, chaque élément <script> doit comporter un attribut nonce correspondant à la valeur de nonce aléatoire spécifiée dans l'en-tête de la 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 les scripts sources de manière dynamique
Vous pouvez charger des scripts tiers de manière dynamique à l'aide d'un script intégré.
<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>{HASHED_INLINE_SCRIPT}. Pour réduire le nombre de hachages, vous pouvez fusionner tous les scripts intégrés dans un seul script. Pour voir une démonstration, consultez cet exemple et son code.
<script src="https://example.org/foo.js"></script> <script src="https://example.org/bar.js"></script>
integrity correspondant à une source autorisée.
Points à prendre en compte concernant le chargement des scripts
L'exemple de script intégré ajoute s.async = false pour s'assurer que foo s'exécute avant bar, même si bar se charge en premier. Dans cet extrait, s.async = false ne bloque pas l'analyseur pendant le chargement des scripts, car ceux-ci sont ajoutés de manière dynamique. L'analyseur ne s'arrête que pendant l'exécution des scripts, comme il le ferait pour les scripts async. Toutefois, tenez compte des points suivants concernant cet extrait :
-
Un ou les deux scripts peuvent s'exécuter avant que le téléchargement du document ne soit terminé. Si vous souhaitez que le document soit prêt au moment de l'exécution des scripts, attendez l'événement
DOMContentLoadedavant d'ajouter les scripts. Si cela entraîne un problème de performances, car les scripts ne commencent pas à se télécharger assez tôt, utilisez des balises de préchargement plus tôt sur la page. -
defer = truene fait rien. Si vous avez besoin de ce comportement, exécutez le script manuellement lorsque cela est nécessaire.
Étape 3 : Refactoriser les modèles HTML et le code côté client
Les gestionnaires d'événements intégrés (tels que onclick="…", onerror="…") et les URI JavaScript (<a href="javascript:…">) peuvent être utilisés pour exécuter des scripts. 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 modèles, vous devrez le refactoriser en utilisant des alternatives plus sûres.
Si vous avez activé la CSP à l'étape précédente, vous pourrez voir les cas de non-respect de la CSP dans la console chaque fois que la 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 code JavaScript
Si votre application utilise eval() pour convertir des sérialisations de chaînes JSON en objets JS, vous devez refactoriser ces instances en JSON.parse(), 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 d'autres exemples de ce type de refactorisation dans cet atelier de programmation sur la CSP stricte :
Étape 4 (facultative) : Ajoutez des solutions de remplacement pour prendre en charge les anciennes versions de navigateur
Browser Support
Si vous devez prendre en charge des versions de navigateur plus anciennes :
- L'utilisation de
strict-dynamicnécessite l'ajout dehttps:comme solution de remplacement pour les versions antérieures de Safari. Dans ce cas :- Tous les navigateurs compatibles avec
strict-dynamicignorent l'option de remplacementhttps:. Cela ne réduit donc pas la force de la règle. - Dans les anciens navigateurs, les scripts provenant de sources 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, comme les injections d'URI
javascript:.
- Tous les navigateurs compatibles avec
- Pour assurer la compatibilité avec les versions de navigateur très anciennes (plus de quatre ans), vous pouvez ajouter
unsafe-inlinecomme solution de secours. Tous les navigateurs récents ignorentunsafe-inlinesi 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éployez 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 déployer votre CSP 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 utile pour tester une modification potentiellement destructive, comme une nouvelle CSP en production, avant de commencer à appliquer les restrictions de la CSP. En mode "Rapport uniquement", votre CSP n'affecte pas le comportement de votre application, mais le navigateur génère toujours des erreurs de console et des rapports de non-respect lorsqu'il rencontre des modèles incompatibles avec votre CSP. Vous pouvez ainsi voir ce qui aurait été défaillant pour vos utilisateurs finaux. Pour en savoir plus, consultez API Reporting. - Lorsque vous êtes certain que votre CSP ne perturbera pas le fonctionnement de votre site pour les utilisateurs finaux, déployez-le à 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 cette méthode est plus sécurisée qu'une balise<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 efficace qui permet de limiter les attaques XSS. Dans la plupart des cas, la 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'), il existe des cas où la CSP ne protège pas aussi bien votre application :
- Si vous utilisez un nonce pour un script, mais qu'une injection est effectuée directement dans le corps ou le paramètre
srcde cet élément<script>. - S'il existe des injections dans les emplacements des scripts créés de manière dynamique (
document.createElement('script')), y compris dans les fonctions de bibliothèque qui créent des nœuds DOMscripten 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 présentes dans d'anciennes applications AngularJS. Un pirate informatique capable d'injecter du code dans un modèle AngularJS peut l'utiliser pour exécuter du code JavaScript arbitraire.
- Si la règle contient
'unsafe-eval', les injections danseval(),setTimeout()et quelques autres API rarement utilisées.
Les développeurs et les ingénieurs en sécurité doivent prêter une attention particulière à ces modèles lors des revues de code et des audits de sécurité. Pour en savoir plus sur ces cas, consultez Content Security Policy: A Successful Mess Between Hardening and Mitigation.
Documentation complémentaire
- CSP Is Dead, Long Live CSP! On the Insecurity of Whitelists and the Future of Content Security Policy
- Évaluateur CSP
- Conférence LocoMoco : Content Security Policy : un mélange réussi entre renforcement et atténuation
- Conférence Google I/O : Sécuriser les applications Web avec les fonctionnalités de la plate-forme moderne