Previeni le vulnerabilità cross-site scripting basate su DOM con TrustedType

Krzysztof Kotowicz
Krzysztof Kotowicz

Browser Support

  • Chrome: 83.
  • Edge: 83.
  • Firefox: behind a flag.
  • Safari: 26.

Source

Il cross-site scripting basato sul DOM (DOM XSS) si verifica quando i dati provenienti da una origine controllata dall'utente (come un nome utente o un URL di reindirizzamento tratto dal frammento di URL) raggiungono un sink, ovvero una funzione come eval() o un setter di proprietà come .innerHTML che può eseguire codice JavaScript arbitrario.

Il cross-site scripting (XSS) basato sul DOM è una delle vulnerabilità di sicurezza web più comuni ed è frequente che i team di sviluppo lo introducano accidentalmente nelle loro app. Trusted Types ti offre gli strumenti per scrivere, rivedere la sicurezza e mantenere le applicazioni prive di vulnerabilità XSS basate su DOM rendendo sicure per impostazione predefinita le funzioni API web pericolose. Trusted Types è disponibile come polyfill per i browser che non lo supportano ancora.

Sfondo

Per molti anni, DOM XSS è stata una delle vulnerabilità di sicurezza web più diffuse e pericolose.

Esistono due tipi di cross-site scripting. Alcune vulnerabilità XSS sono causate da codice lato server che crea in modo non sicuro il codice HTML che forma il sito web. Altri hanno una causa principale sul client, dove il codice JavaScript chiama funzioni pericolose con contenuti controllati dall'utente.

Per prevenire XSS lato server, non generare HTML concatenando stringhe. Utilizza invece librerie di modelli con escape automatico contestuale sicuro, insieme a una Content Security Policy basata su nonce per una maggiore mitigazione dei bug.

Ora i browser possono anche contribuire a prevenire gli attacchi XSS (cross-site scripting) basati su DOM lato client utilizzando Trusted Types.

Introduzione alle API

I tipi attendibili funzionano bloccando le seguenti funzioni sink rischiose. Potresti già riconoscerne alcuni, perché i fornitori di browser e framework web ti impediscono già di utilizzare queste funzionalità per motivi di sicurezza.

I tipi attendibili richiedono l'elaborazione dei dati prima di passarli a queste funzioni sink. L'utilizzo di una sola stringa non va a buon fine perché il browser non sa se i dati sono attendibili:

Cosa non fare
anElement.innerHTML  = location.href;
Con Trusted Types abilitato, il browser genera un TypeError e impedisce l'utilizzo di un sink DOM XSS con una stringa.

Per indicare che i dati sono stati elaborati in modo sicuro, crea un oggetto speciale: un tipo attendibile.

Cosa fare
anElement.innerHTML = aTrustedHTML;
  
Con i tipi attendibili attivati, il browser accetta un oggetto TrustedHTML per i sink che prevedono snippet HTML. Sono disponibili anche gli oggetti TrustedScript e TrustedScriptURL per altri lavandini sensibili.

Trusted Types riduce notevolmente la superficie di attacco XSS (cross-site scripting) basata su DOM della tua applicazione. Semplifica le revisioni della sicurezza e ti consente di applicare i controlli di sicurezza basati sul tipo eseguiti durante la compilazione, il linting o il bundling del codice in fase di runtime, nel browser.

Come utilizzare i tipi attendibili

Prepararsi ai report sulle violazioni delle norme di sicurezza dei contenuti

Puoi implementare un raccoglitore di report, ad esempio reporting-api-processor o go-csp-collector open source, oppure utilizzare uno degli equivalenti commerciali. Puoi anche aggiungere la registrazione personalizzata e il debug delle violazioni nel browser utilizzando un 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();

o aggiungendo un listener di eventi:

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

Aggiungere un'intestazione CSP solo per i report

Aggiungi la seguente intestazione della risposta HTTP ai documenti di cui vuoi eseguire la migrazione a Trusted Types:

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

Ora tutte le violazioni vengono segnalate a //my-csp-endpoint.example, ma il sito web continua a funzionare. La sezione successiva spiega come funziona //my-csp-endpoint.example.

Identificare le violazioni di Trusted Types

D'ora in poi, ogni volta che Trusted Types rileva una violazione, il browser invia un report a un report-uri configurato. Ad esempio, quando la tua applicazione trasmette una stringa a innerHTML, il browser invia il seguente report:

{
"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"
}
}

Questo messaggio indica che in https://my.url.example/script.js alla riga 39, innerHTML è stato chiamato con la stringa che inizia con <img src=x. Queste informazioni dovrebbero aiutarti a restringere il campo delle parti di codice che potrebbero introdurre DOM XSS e che devono essere modificate.

Correggere le violazioni

Esistono un paio di opzioni per correggere una violazione di Trusted Types. Puoi rimuovere il codice incriminato, utilizzare una libreria, creare una policy Trusted Types o, come ultima risorsa, creare una policy predefinita.

Riscrivere il codice incriminato

È possibile che il codice non conforme non sia più necessario o possa essere riscritto senza le funzioni che causano le violazioni:

Cosa fare
el.textContent = '';
const img = document.createElement('img');
img.src = 'xyz.jpg';
el.appendChild(img);
Cosa non fare
el.innerHTML = '<img src=xyz.jpg>';

Utilizzare una libreria

Alcune librerie generano già tipi attendibili che puoi passare alle funzioni sink. Ad esempio, puoi utilizzare DOMPurify per sanificare uno snippet HTML, rimuovendo i payload XSS.

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

DOMPurify supporta i tipi attendibili e restituisce HTML pulito racchiuso in un oggetto TrustedHTML in modo che il browser non generi una violazione.

Creare una policy Trusted Types

A volte non puoi rimuovere il codice che causa la violazione e non esiste una libreria per sanificare il valore e creare un tipo attendibile per te. In questi casi, puoi creare autonomamente un oggetto Trusted Type.

Innanzitutto, crea un criterio. Le policy sono fabbriche di Trusted Types che applicano determinate regole di sicurezza al loro input:

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

Questo codice crea un criterio denominato myEscapePolicy che può produrre oggetti TrustedHTML utilizzando la funzione createHTML(). Le regole definite eseguono l'escape HTML dei caratteri < per impedire la creazione di nuovi elementi HTML.

Utilizza la norma nel seguente modo:

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)>'

Utilizzare una norma predefinita

A volte non puoi modificare il codice incriminato, ad esempio se stai caricando una libreria di terze parti da una CDN. In questo caso, utilizza un criterio predefinito:

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

Il criterio denominato default viene utilizzato ovunque venga utilizzata una stringa in un sink che accetta solo il tipo attendibile.

Passare all'applicazione di Content Security Policy

Quando la tua applicazione non genera più violazioni, puoi iniziare a applicare i tipi attendibili:

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

Ora, indipendentemente dalla complessità della tua applicazione web, l'unica cosa che può introdurre una vulnerabilità DOM XSS è il codice in uno dei tuoi criteri e puoi renderlo ancora più sicuro limitando la creazione di criteri.

Per approfondire