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 la porte au 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 exécutable : innerHTML, document.write, eval, le constructeur Function, et quelques dizaines d'autres. Une fois Trusted Types appliqué, ces sinks rejettent les chaînes brutes. 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 l'article donne le mode d'emploi, avec 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 le sink eval en particulier. Pour les problèmes de script inline et de chaîne convertie en code que Trusted Types vient compléter, 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 de 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. Vous obtenez ainsi un seul endroit auditable où chaque valeur entrant dans un sink est vérifiée, et le motif dangereux échoue bruyamment au lieu de s'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 aux sinks DOM liés aux scripts :
Content-Security-Policy: require-trusted-types-for 'script'Avec ce header, affecter une chaîne brute à innerHTML (ou à tout sink couvert) lève une TypeError et émet un rapport de violation. Rien d'autre ne change. 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 indique quelles politiques Trusted Types la page a le droit de créer. Une politique est un petit objet doté de fonctions createHTML, createScript ou createScriptURL qui vérifient 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 modifier. À n'utiliser qu'en connaissance de cause, 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 faites passer par elle chaque écriture de sink. 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); // runsLe point de contrôle, c'est la valeur, 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 casse tout ce qui écrit une chaîne brute dans un sink couvert, et sur la plupart des applications cela fait beaucoup d'endroits dont vous ignorez encore l'existence. Procédez par étapes et surveillez les rapports avant d'appliquer : c'est la même approche Report-Only d'abord que pour toute modification de CSP, décrite 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 (faites-le passer 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 prend en charge Trusted Types nativement. 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 fonctionner avec l'application active 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 à protéger. 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 suivi plus récemment, et les directives sont désormais disponibles dans les versions actuelles de Chrome, Firefox et Safari. La prise en charge hors de Chromium est restée longtemps limitée, considérez donc l'application généralisée sur tous les navigateurs comme récente.
La dégradation se fait sans risque. 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 ; la protection n'y joue simplement pas. Vous pouvez donc l'activer dès maintenant et être protégé partout où le navigateur le permet.
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. Faites passer 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 repérer 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 la directive définie, ces sinks rejettent les chaînes brutes 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 d'attaque, gardez donc des contrôles stricts dans cette politique par défaut.
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 quand l'application est active, 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