Previeni gli attacchi XSS (cross-site scripting) basati su DOM con Trusted Types

Funzionalità web: Trusted Types

Trusted Types è una funzionalità di sicurezza che aiuta a prevenire il cross-site scripting basato su DOM (DOM XSS) richiedendo che i dati passati alle API del browser "pericolose" (note come sink) vengano prima convertiti in un tipo specifico, ad esempio TrustedHTML.

Per impostazione predefinita, molte API DOM (come innerHTML o document.write) accettano stringhe non elaborate. Se una stringa contiene uno script dannoso proveniente da una fonte non attendibile, può essere eseguita. I Tipi attendibili modificano questo comportamento: una volta attivati tramite CSP, questi sink accetteranno solo oggetti TrustedHTML creati da policy autorizzate.

Dettagli chiave dell'implementazione

Per utilizzare i Tipi attendibili, segui quattro passaggi principali:

  1. Definisci una policy di sicurezza che specifichi come eseguire la sanificazione o la trasformazione delle stringhe.
  2. Utilizza questa policy per creare oggetti Trusted Type da stringhe non elaborate.
  3. Utilizza gli oggetti Trusted Type durante la scrittura nei sink DOM.
  4. Applica i Trusted Types utilizzando una Content Security Policy (CSP).

1. Creazione di una policy

Una policy è un insieme di regole per la sanitizzazione dei contenuti. Puoi crearlo utilizzando trustedTypes.createPolicy(), in genere delegando a una libreria di sanitizzazione come DOMPurify o alle regole di escape HTML della tua applicazione:

import DOMPurify from 'dompurify';

const myPolicy = trustedTypes.createPolicy('sanitize-html', {
  createHTML: (input) =>
    // Return a sanitized string; createPolicy() wraps it in a TrustedHTML object.
    // Example using DOMPurify:
    DOMPurify.sanitize(input, { RETURN_TRUSTED_TYPE: false }),
});

// Example using HTML escaping (for environments without third-party sanitizers):
const escapePolicy = trustedTypes.createPolicy('escape-html', {
  createHTML: (input) =>
    String(input)
      .replaceAll('&', '&')
      .replaceAll('<', '&lt;')
      .replaceAll('>', '&gt;')
      .replaceAll('"', '&quot;')
      .replaceAll("'", '&#39;'),
});

2. Utilizzo della policy

Anziché passare una stringa direttamente a un sink, chiami il metodo di creazione della policy.

const untrustedInput = '<p>Hello, <strong>world</strong>!</p><img src="x" onerror="alert(1)">';

// In an enforced environment, this would throw a TypeError:
// element.innerHTML = untrustedInput;

// This returns a TrustedHTML object with unsafe elements and attributes removed:
const cleanHTML = myPolicy.createHTML(untrustedInput);
// Result: "<p>Hello, <strong>world</strong>!</p><img src=\"x\">"

3. Transizione dei sink per l'utilizzo di Trusted Types

Aggiorna ogni istanza in cui scrivi in un sink per convertirla prima in un oggetto Trusted Type. Per individuare più facilmente i punti in cui il codice deve essere aggiornato, puoi definire una policy default temporanea che registra quando un sink viene scritto con una stringa anziché con un TrustedType.

trustedTypes.createPolicy("default", {
  createHTML(value) {
    console.warn("String passed instead of Trusted Type.");
    return DOMPurify.sanitize(value, { RETURN_TRUSTED_TYPE: false });
  },
});

I sink comuni che supportano i Trusted Types includono:

  • HTML: element.innerHTML, element.outerHTML, document.write(), element.setHTMLUnsafe(), Document.parseHTMLUnsafe()
  • Script: <script> contenuti di testo, eval()
  • ScriptURL: <script src>, Worker(), SharedWorker()
// Assign the TrustedHTML object.
element.innerHTML = cleanHTML;

Tieni presente che i metodi di sanitizzazione HTML "sicuri" (element.setHTML() e Document.parseHTML(), trattati in Sanitizzazione dell'HTML non attendibile) non richiedono una policy Trusted Types, in quanto sanitizzano sempre i potenziali attacchi XSS in modo nativo.

4. Applicazione di Trusted Types con CSP

I Trusted Types devono essere implementati in modo incrementale per evitare di interrompere l'applicazione. Sebbene l'applicazione completa offra la protezione più efficace, comporta il rischio di interruzione del sito se non è stata eseguita la migrazione di ogni scrittura sink.

Prerequisiti

  • Cooperazione del framework: se il framework della tua applicazione (o qualsiasi libreria di terze parti che scrive in sink DOM) non produce valori TrustedHTML o TrustedScript, non puoi applicare i tipi attendibili senza interrompere il codice. Controlla il supporto del framework prima di iniziare l'implementazione.
  • Rifattorizzazione dei sink: assicurati di aver identificato e rifattorizzato i sink pericolosi per utilizzare le policy dei Tipi attendibili.

Strategia di implementazione

  1. Solo generazione di report: per prima cosa, esegui l'implementazione utilizzando l'intestazione Content-Security-Policy-Report-Only. In questo modo, le violazioni verranno registrate nell'endpoint di reporting senza bloccare effettivamente le scritture.

    Content-Security-Policy-Report-Only: require-trusted-types-for 'script';
    
  2. Applicazione: una volta integrate le policy e quando le segnalazioni di violazioni scendono a zero, passa all'applicazione completa.

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

Puoi anche limitare le policy che possono essere create (elenca i nomi delle policy, ad esempio sanitize-html o escape-html, includi dompurify se la tua policy delega a DOMPurify.sanitize(), che crea internamente una policy dompurify, e includi default se registri una policy predefinita durante la migrazione):

Content-Security-Policy: require-trusted-types-for 'script'; trusted-types sanitize-html escape-html dompurify;

Con questo CSP, qualsiasi scrittura in un sink che non utilizza Trusted Types genererà un errore. Inoltre, il tentativo di creare una policy Trusted Types con un nome non elencato in trusted-types genererà un errore.

Puoi anche impostare questo CSP come tag <meta> nell'elemento <head> del documento, anche se questa opzione è potenzialmente meno sicura.

<!-- First child of `<head>`, as earlier tags will not enforce this CSP. -->
<meta
  http-equiv="Content-Security-Policy"
  content="require-trusted-types-for 'script'; trusted-types sanitize-html escape-html dompurify;"
/>

Strategie di riserva

Per i browser che non supportano ancora i Tipi attendibili, puoi utilizzare un "tinyfill" minimo per assicurarti che il codice non si interrompa, pur continuando a fornire la sanitizzazione.

if (!window.trustedTypes) {
  window.trustedTypes = {
    createPolicy: (name, rules) => rules,
  };
}

In questo modo, puoi scrivere il codice utilizzando il pattern Trusted Types già oggi. I browser che supportano i tipi li applicheranno, mentre i browser meno recenti eseguiranno comunque la logica di sanitizzazione tramite la policy fittizia.

Se un browser non supporta i Tipi attendibili, tratterà semplicemente gli oggetti TrustedHTML come stringhe (se utilizza il tinyfill) o non avrà il controllo di applicazione a livello di sink.