Comment activer Trusted Types avec une CSP pour stopper le XSS DOM
CentralCSP Team ·
Dernière mise à jour:
Trusted Types est la partie d'une politique de sécurité du contenu (CSP) qui ferme le cross-site scripting (XSS) basé sur le DOM. Une CSP est un header de réponse HTTP qui indique au navigateur quels scripts peuvent s'exécuter, et Trusted Types étend cela aux sinks du DOM qui transforment une chaîne en markup ou en code vivant : innerHTML, document.write, eval, le constructeur Function, et quelques dizaines d'autres. Avec Trusted Types appliqué, ces sinks rejettent une chaîne brute. Les seules valeurs qu'ils acceptent sont des objets typés produits par vos propres politiques, donc une chaîne injectée n'atteint jamais un sink.
La version courte : réglez require-trusted-types-for sur 'script' pour activer l'application, utilisez la directive trusted-types pour autoriser les noms de politiques qui peuvent créer ces valeurs typées, et déployez d'abord en Report-Only pour voir chaque sink avant d'en bloquer un. Le reste de cet article est le mode d'emploi, y compris les schémas par framework pour Angular, React et DOMPurify.
Cet article s'appuie sur trusted-types-eval, une façon plus sûre d'autoriser eval dans une CSP, qui couvre spécifiquement le sink eval. Pour les problèmes de script inline et de chaîne-vers-code à côté desquels Trusted Types se situe, voyez pourquoi vous ne devriez jamais utiliser unsafe-inline dans une CSP et unsafe-eval et comment le supprimer.
Ce contre quoi Trusted Types protège
Le XSS DOM survient quand du code côté client passe du texte contrôlé par l'attaquant dans un sink qui le parse comme du HTML ou l'exécute comme du script. Un cas classique est element.innerHTML = location.hash. Les défenses côté serveur ne le voient jamais, parce que l'affectation dangereuse a lieu dans le navigateur, après le chargement de la page, à partir de données que le serveur n'a peut-être jamais touchées.
Trusted Types change la règle au niveau du sink. Quand l'application est active, un sink d'injection n'accepte plus du tout une chaîne. Il accepte un objet TrustedHTML, TrustedScript ou TrustedScriptURL, et le seul moyen d'en fabriquer un est de faire passer une chaîne par une fonction de politique enregistrée que vous avez écrite. Cela vous donne un seul endroit auditable où chaque valeur entrant dans un sink est vérifiée, et fait échouer le motif dangereux bruyamment au lieu de l'exécuter.
Activer l'application avec require-trusted-types-for
Une seule directive active Trusted Types. La directive require-trusted-types-for prend la valeur unique 'script', qui indique au navigateur d'appliquer Trusted Types au niveau des sinks DOM liés aux scripts :
Content-Security-Policy: require-trusted-types-for 'script'Avec ce header présent, affecter une chaîne brute à innerHTML (ou à tout sink couvert) lève une TypeError et émet un rapport de violation. Rien d'autre ne fonctionne différemment. Cette directive n'a pas de repli vers default-src, elle ne s'applique donc que lorsque vous la définissez explicitement.
Autoriser vos politiques avec la directive trusted-types
L'application seule bloquerait aussi votre propre code, parce que votre code écrit lui aussi dans ces sinks. La directive trusted-types nomme quelles politiques Trusted Types la page est autorisée à créer. Une politique est un petit objet avec des fonctions createHTML, createScript ou createScriptURL qui contrôlent une chaîne et renvoient la valeur typée.
Listez les noms de politiques que vous comptez enregistrer :
Content-Security-Policy: trusted-types myPolicyQuelques tokens valent la peine d'être connus :
'none'interdit toute création de politique, le réglage le plus strict, utile une fois que vous avez supprimé chaque écriture directe dans un sink.*autorise n'importe quel nom de politique unique (moins strict, pratique pendant le déploiement).'allow-duplicates'permet d'enregistrer plusieurs fois le même nom de politique, ce dont certains bundlers et micro-frontends ont besoin.defaultest le nom réservé à la politique par défaut. Si vous enregistrez une politique nommée"default", le navigateur exécute automatiquement sa fonctioncreate*sur toute chaîne brute passée à un sink, ce qui permet d'adapter Trusted Types à du code que vous ne pouvez pas éditer. Utilisez-le délibérément, car une politique par défaut faible rouvre la surface d'attaque.
Combinez les deux directives en une seule politique, une directive par ligne pour la lisibilité :
Content-Security-Policy:
require-trusted-types-for 'script';
trusted-types myPolicyEnregistrer une politique dans votre code
Une politique est l'endroit où vous mettez la véritable sanitisation. Créez-la une fois, puis routez chaque écriture de sink à travers elle. Voici une politique minimale qui sanitise du HTML avant qu'il ne devienne un TrustedHTML :
// Register a named policy. The name must be in the trusted-types directive.
const policy = window.trustedTypes.createPolicy("myPolicy", {
createHTML: (input) => {
// Do the real sanitization here. Returning input untouched is not safe;
// the policy is only as strong as the check inside it.
return sanitize(input);
},
});
// A plain string is now rejected at the sink.
element.innerHTML = userInput; // throws TypeError under enforcement
// A TrustedHTML from your policy is accepted.
element.innerHTML = policy.createHTML(userInput); // runsLa valeur est le point de passage, pas l'affectation. Chaque chaîne qui devient du markup passe par createHTML, vous avez donc une seule fonction à relire, tester et durcir, au lieu d'auditer chaque innerHTML dans la base de code.
Déployez-le d'abord en Report-Only
Activer l'application sur un site en production cassera tout ce qui écrit une chaîne brute dans un sink couvert, ce qui, sur la plupart des applications, représente beaucoup d'endroits que vous ne connaissez pas encore. Faites-le par étapes et surveillez les rapports avant d'appliquer, la même approche Report-Only d'abord que pour toute modification CSP couverte dans comment construire une CSP solide, étape par étape.
Déployez les directives sur le header Content-Security-Policy-Report-Only. Le navigateur ne bloque rien ; il envoie un rapport de violation pour chaque écriture de sink que l'application aurait rejetée.
Content-Security-Policy-Report-Only:
require-trusted-types-for 'script';
trusted-types myPolicy;
report-to csp-endpointCâblez l'endpoint avec le header Reporting-Endpoints :
Reporting-Endpoints: csp-endpoint="https://<Endpoint-ID>.report.centralcsp.com"Les violations Trusted Types n'introduisent pas de nouveau type de rapport. Elles arrivent comme des rapports de violation CSP standard, atterrissent donc au même endpoint et se lisent avec les mêmes champs effectiveDirective, sample et sourceFile que tout autre script bloqué. Collectez-les, corrigez chaque sink (routez-le par une politique ou supprimez-le), et ne basculez vers le header Content-Security-Policy appliqué qu'une fois le Report-Only silencieux.
CentralCSP ingère ces rapports Report-Only et les regroupe par page et par sink, pour que vous voyiez quelles parties de l'application écrivent encore des chaînes brutes avant d'appliquer. Vous pouvez aussi vérifier les points faibles d'un brouillon de politique avec l'évaluateur CSP gratuit avant de le déployer.
Schémas par framework
La plupart des applications n'appellent pas directement les sinks ; un framework le fait pour elles. Le travail Trusted Types porte alors surtout sur la façon dont ce framework obtient sa fonction create*.
Angular
Angular dispose d'un support Trusted Types intégré. Son DomSanitizer passe déjà par une politique Trusted Types, nommée angular pour le framework et angular#bundler pour la sortie de build, donc une application Angular peut tourner sous application une fois que vous autorisez ces noms de politiques dans la directive. Listez ceux que votre build utilise :
Content-Security-Policy:
require-trusted-types-for 'script';
trusted-types angular angular#bundlerAngular enregistre angular et angular#bundler comme noms de politiques de base. Selon votre build, il peut aussi enregistrer angular#unsafe-bypass, angular#unsafe-jit ou angular#unsafe-upgrade, alors surveillez vos rapports Report-Only et autorisez ceux que votre build utilise réellement. Si vous appelez vous-même des sinks hors des API d'Angular, enregistrez votre propre politique supplémentaire et ajoutez son nom à la liste.
React avec DOMPurify
React échappe le texte par défaut, mais dangerouslySetInnerHTML écrit directement dans le DOM, c'est donc le sink à garder. DOMPurify sanitise du HTML et peut renvoyer directement une valeur Trusted Types avec l'option RETURN_TRUSTED_TYPE, ce qui signifie que la sortie sanitisée est déjà un TrustedHTML produit par votre politique :
import DOMPurify from "dompurify";
// DOMPurify creates a Trusted Types policy named "dompurify" internally.
const clean = DOMPurify.sanitize(dirtyHtml, { RETURN_TRUSTED_TYPE: true });
// clean is a TrustedHTML, accepted by the sink under enforcement.
<div dangerouslySetInnerHTML={{ __html: clean }} />;Autorisez la politique que DOMPurify enregistre (dompurify) plus toutes les vôtres :
Content-Security-Policy:
require-trusted-types-for 'script';
trusted-types dompurify myPolicyCe schéma fonctionne pour tout framework qui expose un sink HTML brut : sanitisez avec DOMPurify en mode RETURN_TRUSTED_TYPE, passez le résultat typé au sink, et autorisez dompurify.
Le support des navigateurs aujourd'hui
Trusted Types est le plus abouti dans Chromium, qui livre require-trusted-types-for et trusted-types depuis des années. Firefox a ajouté le support plus récemment, et les directives sont récemment devenues disponibles dans les versions actuelles de Chrome, Firefox et Safari. Le support hors de Chromium a historiquement été limité, alors traitez l'application multi-navigateurs généralisée comme récente.
Le comportement se dégrade en toute sécurité. Un navigateur qui n'applique pas Trusted Types ignore les directives et exécute la page comme avant, donc les déployer ne casse pas les clients plus anciens ; vous n'obtenez simplement pas la protection là. Cela permet de l'activer dès maintenant et de gagner la protection partout où le navigateur la prend en charge.
Foire aux questions
Comment activer Trusted Types ?
Ajoutez require-trusted-types-for 'script' à votre CSP pour activer l'application, et ajoutez une directive trusted-types listant les noms de politiques que votre code crée. Routez chaque écriture de sink DOM par l'une de ces politiques, et déployez-la d'abord sur le header Content-Security-Policy-Report-Only pour attraper chaque sink avant d'en bloquer un.
Que fait require-trusted-types-for ?
Elle indique au navigateur d'appliquer Trusted Types aux sinks DOM liés aux scripts comme innerHTML, document.write et eval. Sa seule valeur est le token 'script'. Une fois définie, ces sinks rejettent une chaîne brute et n'acceptent qu'un objet typé produit par l'une de vos politiques enregistrées.
Qu'est-ce que la politique Trusted Types par défaut ?
Une politique enregistrée avec le nom réservé "default". Le navigateur exécute automatiquement sa fonction create* sur toute chaîne brute passée à un sink, ce qui adapte Trusted Types à du code que vous ne pouvez pas changer. C'est pratique mais cela élargit la surface, alors gardez les contrôles de la politique par défaut stricts.
Puis-je utiliser Trusted Types avec React ?
Oui. Gardez dangerouslySetInnerHTML en sanitisant avec DOMPurify en mode RETURN_TRUSTED_TYPE, qui renvoie un TrustedHTML que le sink accepte sous application, puis autorisez le nom de politique dompurify dans la directive trusted-types.
À retenir
Activer Trusted Types tient en deux directives et une habitude : require-trusted-types-for 'script' active l'application, trusted-types autorise les politiques qui peuvent créer des valeurs typées, et chaque écriture de sink passe par une politique que vous pouvez auditer. Déployez-le en Report-Only, corrigez les sinks que les rapports font remonter, puis appliquez. Les frameworks font le gros du travail : Angular livre ses propres politiques, et DOMPurify remet à React un TrustedHTML directement.
Si vous voulez instrumenter le déploiement, commencez gratuitement avec CentralCSP, pointez-y un header Report-Only, et observez quels sinks ont encore besoin d'une politique avant d'appliquer.
Sources
- W3C, spécification Trusted Types
- MDN, API Trusted Types
- Angular, sécurité et Trusted Types
- DOMPurify, source et options