Tous les articles

trusted-types-eval, autoriser eval plus prudemment dans une CSP

CentralCSP Team ·

Dernière mise à jour:

Si vous avez besoin que eval() ou le constructeur Function() continuent de fonctionner sous une politique de sécurité du contenu (CSP), la réponse habituelle a longtemps été d'ajouter 'unsafe-eval'. Ce mot-clé réactive la compilation de chaîne en code pour n'importe quelle chaîne, ce qui correspond exactement au comportement que la plupart des attaques par cross-site scripting (XSS) exploitent. Le mot-clé 'trusted-types-eval' est le remplaçant plus sûr : il autorise eval() et Function() uniquement quand les Trusted Types sont appliqués et uniquement quand vous passez un objet TrustedScript au lieu d'une chaîne brute.

En résumé : si vous déployez les Trusted Types et qu'il vous reste un sink eval que vous ne pouvez pas encore retirer, utilisez 'trusted-types-eval' dans script-src plutôt que 'unsafe-eval'. Sur les navigateurs qui appliquent les Trusted Types, la compilation de code doit d'abord passer par une policy. Sur les navigateurs qui ne prennent pas en charge les Trusted Types, eval reste bloqué au lieu d'être totalement ouvert. La suite de cet article explique où se situe le mot-clé, comment il se comporte et comment le mettre en place.

Vous débutez sur cette partie de la CSP ? Comment construire une politique de sécurité du contenu solide couvre les fondations, et Pourquoi vous ne devriez jamais utiliser unsafe-inline dans une CSP explique le problème connexe des scripts inline.

Ce que la CSP bloque par défaut

Une politique de sécurité du contenu est un header de réponse HTTP qui indique au navigateur quels scripts il a le droit d'exécuter. Quand la politique définit script-src (ou se rabat sur default-src), le navigateur bloque les fonctions qui transforment des chaînes en code exécuté :

  • eval()
  • new Function() et le constructeur Function
  • la forme chaîne de setTimeout() et setInterval()

C'est intentionnel. Ces sinks de compilation de chaîne sont un chemin direct entre du texte injecté et du JavaScript exécuté, donc le header de sécurité les désactive tant que vous ne les réactivez pas explicitement.

Les deux façons de les réactiver présentent des risques très différents. La première, 'unsafe-eval', réactive la compilation de chaîne pour chaque chaîne sans aucun contrôle ; voyez unsafe-eval et comment le supprimer. La seconde, 'trusted-types-eval', fait l'objet de cet article.

Ce que fait trusted-types-eval

'trusted-types-eval' est un mot-clé source de script-src. Ce n'est pas une valeur de require-trusted-types-for ni une valeur de trusted-types. Il se place dans la liste de script-src à côté de mots-clés comme 'unsafe-eval' et 'wasm-unsafe-eval'.

Voici comment les deux se comparent :

  • 'unsafe-eval' réactive eval() et Function() pour n'importe quelle chaîne. Risque élevé, et il fonctionne de la même manière, que les Trusted Types existent ou non.
  • 'trusted-types-eval' les réactive uniquement quand les Trusted Types sont appliqués aux scripts, et uniquement quand vous passez un TrustedScript produit par l'une de vos policies, pas une simple chaîne.

La différence pratique apparaît sur les navigateurs qui ne prennent pas en charge les Trusted Types. Avec 'unsafe-eval', ces navigateurs exécutent n'importe quelle chaîne que vous passez à eval(), donc l'absence de la fonctionnalité Trusted Types signifie aucune protection du tout. Avec 'trusted-types-eval', ces navigateurs n'ont rien qui laisse passer eval, donc le sink reste bloqué. Vous obtenez l'eval dont vous avez besoin là où les Trusted Types l'appliquent, et un comportement par défaut sûr partout ailleurs.

Une chose à garder claire : 'trusted-types-eval' à lui seul n'active pas les Trusted Types. Il ne fait que régir les sinks eval et Function dans un contexte déjà appliqué. L'application vient toujours de require-trusted-types-for.

Comment appliquer les Trusted Types pour eval

Deux directives activent les Trusted Types, et la démonstration complète de comment activer les Trusted Types couvre le déploiement. La directive require-trusted-types-for prend la seule valeur 'script' et fait appliquer par le navigateur les Trusted Types sur les sinks d'injection DOM et sur les sinks de compilation de chaîne (eval, Function) :

Content-Security-Policy: require-trusted-types-for 'script'

La directive trusted-types contrôle quels noms de policy vous avez le droit de créer. Elle accepte un ou plusieurs noms de policy, plus des mots-clés optionnels comme 'allow-duplicates' (et 'none' pour interdire toutes les policies, ou * pour autoriser n'importe quel nom unique) :

Content-Security-Policy: trusted-types myPolicy 'allow-duplicates'

Pour autoriser eval() uniquement quand les Trusted Types sont appliqués, combinez les trois dans une seule politique. Une directive par ligne pour la lisibilité :

Content-Security-Policy:
-  script-src 'self' 'unsafe-eval';
+  script-src 'self' 'trusted-types-eval';
  require-trusted-types-for 'script';
  trusted-types myPolicy

Avec cette politique en place, appeler eval() avec une simple chaîne lève une EvalError, même si 'trusted-types-eval' est présent. Vous devez d'abord compiler un TrustedScript via l'une de vos policies enregistrées.

Un exemple concret

Enregistrez une policy Trusted Types qui définit createScript, puis passez sa sortie à eval() :

// Register a policy that vets the string before it becomes code.
const policy = window.trustedTypes.createPolicy('myPolicy', {
  createScript: (input) => {
    // Validate or transform input here. Returning it as-is is not safe;
    // a policy is only as good as the checks you put in it.
    return input;
  },
});

// A plain string is rejected under enforcement.
eval('a = "hello"'); // throws EvalError

// A TrustedScript from the policy is accepted.
const trusted = policy.createScript('a = "hello"');
eval(trusted); // runs

La chaîne doit toujours passer par createScript, ce qui vous donne un seul endroit pour la valider ou l'assainir. C'est tout l'intérêt de l'approche, et aussi sa limite : la policy peut toujours être mal écrite, donc les Trusted Types ne rendent pas le code sûr à eux seuls. Ils garantissent que chaque chaîne compilée est passée par un point de contrôle que vous maîtrisez.

Il y a un raccourci à connaître. Une policy enregistrée avec le nom réservé "default" exécute son createScript sur toute chaîne simple passée à un sink eval, ce qui réautorise eval() avec des chaînes simples sur toute la page. Utilisez-le délibérément, car il réélargit la surface vers le comportement de 'unsafe-eval'.

Quand les violations sont reportées

Les violations Trusted Types n'introduisent pas de nouveau type de report. Elles arrivent comme des reports de violation CSP standard, le même payload que vous recevez déjà pour tout script bloqué, livré à l'endpoint de reporting vers lequel pointe votre politique. Les valeurs exactes de effectiveDirective et sample émises pour un blocage Trusted Types spécifique à eval se confirment le mieux avec une capture en direct ; la forme générale du report (documentURL, blockedURL, effectiveDirective, sample) est le corps de violation CSP standard.

Envoyez ces reports quelque part où vous pouvez les lire. Ajoutez un endpoint de reporting à votre politique :

Content-Security-Policy:
  script-src 'self' 'trusted-types-eval';
  require-trusted-types-for 'script';
  trusted-types myPolicy;
  report-to csp-endpoint
Reporting-Endpoints: csp-endpoint="https://<Endpoint-ID>.report.centralcsp.com"

Déployer les Trusted Types sur un vrai site signifie généralement activer d'abord l'application en Report-Only, observer quels sinks se déclenchent, puis les corriger avant d'appliquer. CentralCSP ingère ces reports et les regroupe pour que vous voyiez quelles pages dépendent encore d'eval et quelles policies sont créées. Vous pouvez vérifier une politique à la recherche de mots-clés eval et d'autres risques avec le évaluateur CSP gratuit avant de la déployer.

Faut-il l'utiliser du tout

'trusted-types-eval' est un mot-clé plus sûr que 'unsafe-eval', mais la politique la plus sûre n'a ni l'un ni l'autre. Exécuter du code à partir de chaînes reste risqué, et une seule erreur dans une policy peut encore laisser passer une injection.

Un ordre de préférence raisonnable :

  1. Retirez l'eval. Remplacez la compilation dynamique de chaîne par JSON.parse, une table de correspondance, ou du code ordinaire partout où vous le pouvez.
  2. Autorisez des scripts connus précis. Utilisez un nonce ou un hash dans script-src pour autoriser les scripts auxquels vous faites confiance, plutôt que d'ouvrir eval.
  3. Si vous avez réellement besoin d'eval pour l'instant, utilisez 'trusted-types-eval' avec require-trusted-types-for 'script' et une policy stricte, et traitez-le comme une étape temporaire pendant que vous retirez la dépendance.

Prise en charge par les navigateurs aujourd'hui

Les directives require-trusted-types-for et trusted-types sont récemment devenues disponibles dans les versions actuelles de Chrome, Firefox et Safari. Chromium livre les Trusted Types depuis des années, et Firefox a ajouté la prise en charge plus récemment.

Le mot-clé 'trusted-types-eval' est plus récent, et il est récemment devenu disponible dans les versions actuelles de Chrome, Firefox et Safari, en tant qu'ajout normalisé à la grammaire script-src de CSP Level 3. Comme le mot-clé se dégrade en toute sécurité, le déployer sur un navigateur qui ne le reconnaît pas encore laisse eval bloqué, ce qui est le comportement souhaité.

Si vous introduisez les Trusted Types dans une application existante, commencez en Report-Only, routez les reports vers un endroit où vous pouvez les lire, et resserrez à partir de là. Démarrer avec le reporting CSP détaille la configuration du reporting, et vous pouvez démarrer gratuitement pour voir vos reports Trusted Types et eval regroupés sur du trafic réel.

Articles liés