Prévenez les failles des scripts DOM basés sur des scripts intersites grâce aux Trusted Types

Krzysztof Kotowicz
Krzysztof Kotowicz

Navigateurs pris en charge

  • 83
  • 83
  • x
  • x

Source

Les scripts intersites basés sur DOM (DOM XSS) se produisent lorsque les données provenant d'une source contrôlée par l'utilisateur (comme un nom d'utilisateur ou une URL de redirection issue du fragment d'URL) atteignent un récepteur, qui est une fonction comme eval() ou un setter de propriété tel que .innerHTML pouvant exécuter du code JavaScript arbitraire.

Les DOM XSS constituent l'une des failles de sécurité Web les plus courantes. Il est courant que les équipes de développement l'introduisent accidentellement dans leurs applications. Les Trusted Types vous donnent les outils pour écrire, examiner la sécurité et protéger vos applications contre les failles DOM XSS en sécurisant par défaut les fonctions dangereuses des API Web. Les Trusted Types sont disponibles sous forme de polyfill pour les navigateurs qui ne les acceptent pas encore.

Contexte

Depuis de nombreuses années, DOM XSS est l'une des failles de sécurité Web les plus courantes et les plus dangereuses.

Il existe deux types de scripts intersites. Certaines failles XSS sont causées par du code côté serveur qui crée de manière non sécurisée le code HTML qui forme le site Web. D'autres ont une cause première sur le client, où le code JavaScript appelle des fonctions dangereuses avec du contenu contrôlé par l'utilisateur.

Pour empêcher le XSS côté serveur, ne générez pas de code HTML en concaténant des chaînes. Utilisez plutôt des bibliothèques de modèles d'échappement contextuel automatiques sécurisés, ainsi qu'un Content Security Policy basé sur des nonces pour réduire les bugs supplémentaires.

Désormais, les navigateurs peuvent également empêcher les XSS basés sur le DOM côté client à utiliser les Trusted Types.

Présentation de l'API

Les Trusted Types fonctionnent en verrouillant les fonctions de récepteur risquées suivantes. Vous reconnaissez peut-être déjà certains d'entre eux, car les fournisseurs de navigateurs et les frameworks Web vous empêchent déjà d'utiliser ces fonctionnalités pour des raisons de sécurité.

Les Trusted Types requièrent que vous traitez les données avant de les transmettre à ces fonctions de récepteur. L'utilisation d'une chaîne seule échoue, car le navigateur ne sait pas si les données sont fiables:

À éviter
anElement.innerHTML  = location.href;
Lorsque les Trusted Types sont activés, le navigateur génère une erreur TypeError et empêche l'utilisation d'un récepteur DOM XSS avec une chaîne.

Pour indiquer que les données ont été traitées de manière sécurisée, créez un objet spécial : un type de confiance.

À faire
anElement.innerHTML = aTrustedHTML;
  
Lorsque les Trusted Types sont activés, le navigateur accepte un objet TrustedHTML pour les récepteurs qui attendent des extraits HTML. Il existe également des objets TrustedScript et TrustedScriptURL pour les autres récepteurs sensibles.

Les Trusted Types réduisent considérablement la surface d'attaque DOM XSS de votre application. Il simplifie les examens de sécurité et vous permet d'appliquer les contrôles de sécurité par type effectués lors de la compilation, du linting ou du regroupement de votre code au moment de l'exécution dans le navigateur.

Utiliser les Trusted Types

Préparer les signalements de non-respect de Content Security Policy

Vous pouvez déployer un collecteur de rapports, tel que l'outil Open Source reporting-api-processor ou go-csp-collector, ou utiliser l'un des équivalents commerciaux. Vous pouvez également ajouter une journalisation personnalisée et des violations de débogage dans le navigateur à l'aide de ReportingObserver:

const observer = new ReportingObserver((reports, observer) => {
    for (const report of reports) {
        if (report.type !== 'csp-violation' ||
            report.body.effectiveDirective !== 'require-trusted-types-for') {
            continue;
        }

        const violation = report.body;
        console.log('Trusted Types Violation:', violation);

        // ... (rest of your logging and reporting logic)
    }
}, { buffered: true });

observer.observe();

ou en ajoutant un écouteur d'événements:

document.addEventListener('securitypolicyviolation',
    console.error.bind(console));

Ajouter un en-tête CSP pour les rapports uniquement

Ajoutez l'en-tête de réponse HTTP suivant aux documents que vous souhaitez migrer vers les Trusted Types:

Content-Security-Policy-Report-Only: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Désormais, tous les cas de non-respect des règles sont signalés à //my-csp-endpoint.example, mais le site Web continue de fonctionner. La section suivante explique le fonctionnement de //my-csp-endpoint.example.

Identifier les cas de non-respect des Trusted Types

Désormais, chaque fois que les Trusted Types détectent une violation, le navigateur envoie un rapport à un report-uri configuré. Par exemple, lorsque votre application transmet une chaîne à innerHTML, le navigateur envoie le rapport suivant:

{
"csp-report": {
    "document-uri": "https://my.url.example",
    "violated-directive": "require-trusted-types-for",
    "disposition": "report",
    "blocked-uri": "trusted-types-sink",
    "line-number": 39,
    "column-number": 12,
    "source-file": "https://my.url.example/script.js",
    "status-code": 0,
    "script-sample": "Element innerHTML <img src=x"
}
}

Cela indique que dans https://my.url.example/script.js à la ligne 39, innerHTML a été appelé avec la chaîne commençant par <img src=x. Ces informations devraient vous aider à déterminer quelles parties du code peuvent introduire des XSS DOM et doivent être modifiées.

Corriger les cas de non-respect des règles

Vous disposez de deux options pour corriger un cas de non-respect des règles du Trusted Type. Vous pouvez supprimer le code incriminé, utiliser une bibliothèque, créer une règle "Trusted Type" ou, en dernier recours, créer une règle par défaut.

Réécrire le code incriminé

Il est possible que le code non conforme ne soit plus nécessaire ou qu'il puisse être réécrit sans les fonctions à l'origine des cas de non-conformité:

À faire
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
À éviter
el.innerHTML = '<img src=xyz.jpg>';

Utiliser une bibliothèque

Certaines bibliothèques génèrent déjà des Trusted Types que vous pouvez transmettre aux fonctions du récepteur. Par exemple, vous pouvez utiliser DOMPurify pour nettoyer un extrait de code HTML, en supprimant les charges utiles XSS.

import DOMPurify from 'dompurify';
el.innerHTML = DOMPurify.sanitize(html, {RETURN_TRUSTED_TYPE: true});

DOMPurify est compatible avec les Trusted Types et renvoie du code HTML nettoyé encapsulé dans un objet TrustedHTML afin que le navigateur ne génère pas de cas de non-respect des règles.

Créer une règle "Trusted Type"

Parfois, vous ne pouvez pas supprimer le code à l'origine de la violation, et il n'existe aucune bibliothèque permettant de nettoyer la valeur et de créer un type de confiance pour vous. Dans ce cas, vous pouvez créer vous-même un objet Trusted Type.

Commencez par créer une règle. Les règles sont des fabriques de Trusted Types qui appliquent certaines règles de sécurité sur leur entrée:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  const escapeHTMLPolicy = trustedTypes.createPolicy('myEscapePolicy', {
    createHTML: string => string.replace(/\</g, '&lt;')
  });
}

Ce code crée une règle appelée myEscapePolicy qui peut produire des objets TrustedHTML à l'aide de sa fonction createHTML(). Caractères < d'échappement HTML définis pour les règles définies pour empêcher la création d'éléments HTML.

Utilisez la règle comme suit:

const escaped = escapeHTMLPolicy.createHTML('<img src=x onerror=alert(1)>');
console.log(escaped instanceof TrustedHTML);  // true
el.innerHTML = escaped;  // '&lt;img src=x onerror=alert(1)>'

Utiliser une règle par défaut

Parfois, vous ne pouvez pas modifier le code incriminé, par exemple si vous chargez une bibliothèque tierce à partir d'un CDN. Dans ce cas, utilisez une stratégie par défaut:

if (window.trustedTypes && trustedTypes.createPolicy) { // Feature testing
  trustedTypes.createPolicy('default', {
    createHTML: (string, sink) => DOMPurify.sanitize(string, {RETURN_TRUSTED_TYPE: true})
  });
}

La règle nommée default est utilisée chaque fois qu'une chaîne est utilisée dans un récepteur qui n'accepte que le type de confiance.

Passer à l'application de Content Security Policy

Lorsque votre application ne présente plus d'infractions, vous pouvez commencer à appliquer les Trusted Types:

Content-Security-Policy: require-trusted-types-for 'script'; report-uri //my-csp-endpoint.example

Quelle que soit la complexité de votre application Web, la seule chose qui peut introduire une faille DOM XSS est le code de l'une de vos règles. Vous pouvez la verrouiller encore plus en limitant la création de règles.

Complément d'informations