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

Krzysztof Kotowicz
Krzysztof Kotowicz

Supporto dei browser

  • Chrome: 83.
  • Edge: 83.
  • Firefox: non supportato.
  • Safari: non supportato.

Origine

Lo scripting cross-site basato su DOM (DOM XSS) si verifica quando i dati di un elemento di origine controllato dall'utente (ad esempio un nome utente o un URL di reindirizzamento ricavato dal frammento URL) raggiungono un elemento di destinazione, ovvero una funzione come eval() o un settore di proprietà come .innerHTML che può eseguire codice JavaScript arbitrario.

L'XSS DOM è una delle vulnerabilità di sicurezza web più comuni ed è normale che i team di sviluppo la introducano accidentalmente nelle loro app. Tipi attendibili ti forniscono gli strumenti per scrivere, eseguire la revisione della sicurezza e mantenere le applicazioni prive di vulnerabilità XSS DOM rendendo sicure per impostazione predefinita le funzioni API web pericolose. I tipi attendibili sono disponibili come polyfill per i browser che non li supportano ancora.

Sfondo

Per molti anni, gli XSS DOM sono stati 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 evitare attacchi XSS lato server, non generare HTML concatenando stringhe. Utilizza invece librerie di modelli con estrazione automatica contestuale sicura, insieme a un criterio Content Security basato su nonce per una mitigazione aggiuntiva dei bug.

Ora i browser possono anche contribuire a prevenire gli attacchi XSS lato client basati su DOM utilizzando tipi attendibili.

Introduzione all'API

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

I tipi attendibili richiedono di elaborare i dati prima di trasmetterli a queste funzioni di destinazione. 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'uso di un'area di destinazione XSS DOM 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 abilitati, il browser accetta un oggetto TrustedHTML per i sink che si aspettano snippet HTML. Esistono anche gli oggetti TrustedScript e TrustedScriptURL per altri canali 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 esecuzione nel browser.

Come utilizzare i tipi attendibili

Prepararsi ai report sulle violazioni delle norme di sicurezza del contenuto

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 log personalizzati e violazioni di debug 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 gestore di eventi:

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

Aggiungi un'intestazione CSP solo per i report

Aggiungi la seguente intestazione di risposta HTTP ai documenti di cui vuoi eseguire la migrazione ai tipi attendibili:

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

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

Correggere le violazioni

Esistono un paio di opzioni per correggere una violazione di Trusted Type. Puoi rimuovere il codice in questione, utilizzare una libreria, creare un criterio di tipo attendibile o, come ultima soluzione, creare un criterio predefinito.

Riscrivere il codice in questione

È 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 di destinazione. Ad esempio, puoi utilizzare DOMPurify per eseguire la sanitizzazione di 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 sanificato racchiuso in un oggetto TrustedHTML in modo che il browser non generi una violazione.

Creare un criterio di tipo attendibile

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

Per prima cosa, crea un criterio. I criteri sono fabbriche per Trusted Types che applicano determinate regole di sicurezza ai 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 relativa funzione createHTML(). Le regole definite eseguono la codifica HTML dei caratteri < per impedire la creazione di nuovi elementi HTML.

Utilizza il criterio 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 un criterio predefinito

A volte non puoi modificare il codice in questione, ad esempio se carichi una biblioteca 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 delle norme di sicurezza dei contenuti

Quando l'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à dell'applicazione web, l'unica cosa che può introdurre una vulnerabilità XSS DOM è il codice di uno dei tuoi criteri e puoi bloccare ulteriormente questo problema limitando la creazione dei criteri.

Per approfondire