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

Krzysztof Kotowicz
Krzysztof Kotowicz

Browser Support

  • Chrome: 83.
  • Edge: 83.
  • Firefox: 148.
  • Safari: 26.

Source

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

DOM XSS è una delle vulnerabilità di sicurezza web più comuni ed è frequente che i team di sviluppo la introducano accidentalmente nelle loro app. Trusted Types ti offre gli strumenti per scrivere, esaminare la sicurezza e mantenere le applicazioni prive di vulnerabilità DOM XSS 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 dal codice lato server che crea in modo non sicuro il codice HTML che forma il sito web. Altre hanno una causa principale sul client, dove il codice JavaScript chiama funzioni pericolose con contenuti controllati dall'utente.

Per evitare XSS lato server, non generare HTML concatenando stringhe. Utilizza invece librerie di modelli con escape automatico contestuale sicure, insieme a un Content Security Policy basato su nonce per un'ulteriore mitigazione dei bug.

Ora i browser possono anche contribuire a prevenire XSS basato su DOM lato client utilizzando Trusted Types.

Introduzione all'API

Trusted Types funziona bloccando le seguenti funzioni sink rischiose. Potresti già riconoscerne alcune, perché i fornitori di browser e i framework web ti impediscono già di utilizzare queste funzionalità per motivi di sicurezza.

Trusted Types richiede di elaborare i dati prima di passarli a queste funzioni sink. L'utilizzo di una sola stringa non riesce, 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, ovvero un Trusted Type.

Cosa fare
anElement.innerHTML = aTrustedHTML;
  
Con Trusted Types abilitato, il browser accetta un oggetto TrustedHTML per i sink che prevedono snippet HTML. Esistono anche TrustedScript e TrustedScriptURL oggetti per altri sink sensibili.

Trusted Types riduce notevolmente la superficie di attacco DOM XSS della tua applicazione. Semplifica le revisioni di sicurezza e ti consente di applicare i controlli di sicurezza basati sui tipi eseguiti durante la compilazione, il linting o il bundling del codice in fase di runtime, nel browser.

Come utilizzare Trusted Types

Prepararsi per i report sulle violazioni del Content Security Policy

Puoi fare il deployment di un agente di raccolta di report, ad esempio reporting-api-processor open source o go-csp-collector, 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();

oppure aggiungendo un listener di eventi:

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

Aggiungere un'intestazione CSP di sola segnalazione

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 l'applicazione passa 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 indica che in https://my.url.example/script.js alla riga 39 è stato chiamato innerHTML con la stringa che inizia con <img src=x. Queste informazioni dovrebbero aiutarti a restringere le parti di codice che potrebbero introdurre DOM XSS e che devono essere modificate.

Correggere le violazioni

Esistono diverse opzioni per correggere una violazione di Trusted Types. Puoi rimuovere il codice che causa la violazione, utilizzare una libreria, creare una policy di Trusted Types o, come ultima risorsa, creare una policy predefinita.

Riscrivere il codice che causa la violazione

È 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à Trusted Types 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 Trusted Types e restituisce HTML sanificato racchiuso in un oggetto TrustedHTML in modo che il browser non generi una violazione.

Creare una policy di 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 Trusted Type per te. In questi casi, puoi creare un oggetto Trusted Type.

Innanzitutto, crea una policy. Le policy sono factory per Trusted Types che applicano determinate regole di sicurezza all'input:

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

Questo codice crea una policy denominata 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 policy in questo 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 policy predefinita

A volte non puoi modificare il codice che causa la violazione, ad esempio se stai caricando una libreria di terze parti da una CDN. In questo caso, utilizza una policy predefinita:

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

La policy denominata default viene utilizzata ovunque venga utilizzata una stringa in un sink che accetta solo Trusted Type.

Passare all'applicazione del Criterio di sicurezza del contenuto

Quando l'applicazione non genera più violazioni, puoi iniziare ad applicare Trusted Types:

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

Ora, indipendentemente dalla complessità dell'applicazione web, l'unica cosa che può introdurre una vulnerabilità DOM XSS è il codice in una delle tue policy e tu puoi bloccarlo ancora di più limitando la creazione di policy.

Per approfondire