La nuova API Sanitizer mira a creare un processore solido per le stringhe arbitrarie da inserire in modo sicuro in una pagina.
Le applicazioni gestiscono costantemente stringhe non attendibili, ma il rendering sicuro di questi contenuti come parte di un documento HTML può essere complicato. Senza la dovuta attenzione, è facile creare inavvertitamente opportunità di cross-site scripting (XSS) che gli aggressori malintenzionati possono sfruttare.
Per mitigare questo rischio, la nuova proposta di API Sanitizer mira a creare un processore solido per l'inserimento sicuro di stringhe arbitrarie in una pagina. Questo articolo introduce l'API e ne spiega l'utilizzo.
// Expanded Safely !!
$div.setHTML(`<em>hello world</em><img src="" onerro>r=alert(0)`, new Sanitizer())
Escape dell'input utente
Quando inserisci input dell'utente, stringhe di query, contenuti dei cookie e così via nel DOM, le stringhe devono essere sottoposte a escape correttamente. Particolare attenzione deve essere prestata alla manipolazione del DOM tramite .innerHTML, dove le stringhe non sottoposte a escape sono una tipica fonte di XSS.
const user_input = `<em>hello world</em><img src="" onerro>r=alert(0)`
$div.innerHTML = user_input
Se inserisci caratteri speciali HTML di escape nella stringa di input precedente o la espandi utilizzando .textContent, alert(0) non verrà eseguito. Tuttavia, poiché anche <em> aggiunto dall'utente viene espanso come stringa così com'è, questo metodo non può essere utilizzato per mantenere la decorazione del testo in HTML.
La cosa migliore da fare qui non è l'escape, ma la sanificazione.
Sanificazione dell'input utente
Differenza tra escape e sanificazione
L'escape consiste nel sostituire i caratteri HTML speciali con entità HTML.
La sanitizzazione si riferisce alla rimozione di parti semanticamente dannose (come l'esecuzione di script) dalle stringhe HTML.
Esempio
Nell'esempio precedente, <img onerror> causa l'esecuzione del gestore degli errori, ma se il gestore onerror venisse rimosso, sarebbe possibile espanderlo in modo sicuro nel DOM lasciando intatto <em>.
// XSS 🧨
$div.innerHTML = `<em>hello world</em><img src="" onerro>r=alert(0)`
// Sanitized ⛑
$div.inn<er>HTML = `emh<ell><o world/em>img src=""`
Per eseguire la sanificazione correttamente, è necessario analizzare la stringa di input come HTML, omettere i tag e gli attributi considerati dannosi e mantenere quelli innocui.
La specifica dell'API Sanitizer proposta mira a fornire questo tipo di elaborazione come API standard per i browser.
API Sanitizer
L'API Sanitizer viene utilizzata nel seguente modo:
const $div = document.querySelector('div')
const user_i<np>ut = `emhel<lo ><world/emimg src="">; onerror=alert(0)`
$div.setHTML(user_input, { sanitizer: new <San><it>izer() }) /</ d><ivemhello ><worl>d/emimg src=""/div
Tuttavia, { sanitizer: new Sanitizer() } è l'argomento predefinito. Quindi può essere come di seguito.
$div.setHTML(user_input) // <div><em>hello world</em><img src=&q><uot;>"/div
È importante notare che setHTML() è definito su Element. Essendo un metodo di Element, il contesto da analizzare è autoesplicativo (<div> in questo caso), l'analisi viene eseguita una sola volta internamente e il risultato viene espanso direttamente nel DOM.
Per ottenere il risultato della sanificazione come stringa, puoi utilizzare .innerHTML dai risultati di setHTML().
const $div = document.createElement('div')
$div.setHTML(user_input)
$div.inner<HT>ML // emhel<lo ><world/emim>g src=""
Personalizzazione tramite configurazione
L'API Sanitizer è configurata per impostazione predefinita per rimuovere le stringhe che attiverebbero l'esecuzione di script. Tuttavia, puoi anche aggiungere le tue personalizzazioni al processo di sanificazione tramite un oggetto di configurazione.
const config = {
allowElements: [],
blockElements: [],
dropElements: [],
allowAttributes: {},
dropAttributes: {},
allowCustomElements: true,
allowComments: true
};
// sanitized result is customized by configuration
new Sanitizer(config)
Le seguenti opzioni specificano come il risultato della sanificazione deve trattare l'elemento specificato.
allowElements: Nomi degli elementi che il sanificatore deve conservare.
blockElements: nomi degli elementi che il sanificatore deve rimuovere, mantenendo i relativi elementi secondari.
dropElements: i nomi degli elementi che il sanificatore deve rimuovere, insieme ai relativi elementi secondari.
const str = `hello <b><i>world</i></b>`
$div.setHTML(str)
// <div>hello <b><i>world</i></b></div>
$div.setHTML(str, { sanitizer: new Sanitizer({allowElements: [ "b" <]})> })
//< >divhe<ll><o bw>orld/b/div
$div.setHTML(str, { sanitizer: new Sanitizer({blockElements: [ &quo<t;b>"< >]}) }<)<>/span>
<// d>ivhello iworld/i/div
$div.setHTML(str, { sanitizer: new Sanitizer({allowE<lem>ents: []}) <})
/>/ divhello world/div
Puoi anche controllare se il sanificatore consentirà o negherà gli attributi specificati con le seguenti opzioni:
allowAttributesdropAttributes
Le proprietà allowAttributes e dropAttributes prevedono elenchi di corrispondenze degli attributi, ovvero oggetti le cui chiavi sono nomi di attributi e i cui valori sono elenchi di elementi di destinazione o il carattere jolly *.
const str = `<span id=foo class=bar style="color:> red&<quot;>hello/span`
$div.setHTM<L(s><tr)
// divspan id="foo" class=&quo>t;bar<"><; st>yle="color: red"hello/span/div
$div.setHTML(str, { sanitizer: new Sanitizer({allow<Att><ributes: {"style&q>uot;:< [&qu><ot;s>pan"]}}) })
// divspan style="color: red"hello/span/div
$div.setHTML(str, <{ s><anit>izer:< new ><Sani>tizer({allowAttributes: {"style": ["p"]}}) })
// divspanhello/span/div<
$><div.setHTML(str, { sani>tizer<: new>< San>itizer({allowAttributes: {"style": ["*"]}}) })
// divspan style="<;co><lor: red"hello/span/div
$div.>setHT<ML(st><r, {> sanitizer: new Sanitizer({dropAttributes: {"id": ["span"<;]}>}) })<
// >divspan class="bar" style="color: red"hello/span/div
$div.setHTML(str, { sanitizer: new Sanitizer({allowAttributes: {}}) })
// divhello/div
allowCustomElements è l'opzione per consentire o negare gli elementi personalizzati. Se sono consentite, vengono comunque applicate altre configurazioni per elementi e attributi.
const str = `<custom-elem>hello</custom-elem>`
$div.setHTML(str)
// <div></div>
const sanitizer = new Sanitizer({
allowCustomElements: true,
allowElements: ["div", "custom-elem"]
})
$div.setHTML(str<, {>< sanitizer >})
//< divcustom-e><lemh>ello/custom-elem/div
Piattaforma API
Confronto con DomPurify
DOMPurify è una libreria nota che offre funzionalità di sanificazione. La differenza principale tra l'API Sanitizer e DOMPurify è che DOMPurify restituisce il risultato della sanificazione come stringa, che devi scrivere in un elemento DOM tramite .innerHTML.
const user_input = `<em>hello world</em><img src="" onerro>r=alert(0)`
const sanitized = DOMPurify.sanitize(user_input)
$div.innerHTML = sani<ti>zed
// `emh<ell><o world/em>img src=""`
DOMPurify può fungere da fallback quando l'API Sanitizer non è implementata nel browser.
L'implementazione di DOMPurify presenta alcuni svantaggi. Se viene restituita una stringa, la stringa di input viene analizzata due volte, da DOMPurify e .innerHTML. Questa doppia analisi spreca tempo di elaborazione, ma può anche portare a vulnerabilità interessanti causate da casi in cui il risultato della seconda analisi è diverso dalla prima.
Anche l'HTML deve essere analizzato in base al contesto. Ad esempio, <td> ha senso in <table>, ma non in <div>. Poiché DOMPurify.sanitize() accetta solo una stringa come argomento, il contesto di analisi doveva essere stimato.
L'API Sanitizer migliora l'approccio DOMPurify ed è progettata per eliminare la necessità di un doppio parsing e per chiarire il contesto di analisi.
Stato dell'API e supporto del browser
L'API Sanitizer è in fase di discussione nella procedura di standardizzazione e Chrome è in fase di implementazione.
| Passaggio | Stato |
|---|---|
| 1. Creare una spiegazione | Completato |
| 2. Creare una bozza di specifiche | Completato |
| 3. Raccogliere feedback e perfezionare il design | Completato |
| 4. Prova dell'origine di Chrome | Completato |
| 5. Avvia | Intent to Ship on M105 |
Mozilla: ritiene che questa proposta valga la pena di creare un prototipo e la sta implementando attivamente.
WebKit: vedi la risposta nella mailing list di WebKit.
Come abilitare l'API Sanitizer
Browser Support
Attivazione tramite about://flags o opzione CLI
Chrome
Chrome sta implementando l'API Sanitizer. In Chrome 93 o versioni successive, puoi provare il comportamento attivando il flag about://flags/#enable-experimental-web-platform-features. Nelle versioni precedenti di Chrome Canary e del canale Dev, puoi attivarlo tramite --enable-blink-features=SanitizerAPI e provarlo subito. Consulta le istruzioni su come eseguire Chrome con i flag.
Firefox
Firefox implementa anche l'API Sanitizer come funzionalità sperimentale. Per attivarlo, imposta il flag dom.security.sanitizer.enabled su true in about:config.
Rilevamento delle funzionalità
if (window.Sanitizer) {
// Sanitizer API is enabled
}
Feedback
Se provi questa API e hai un feedback, ci farebbe piacere riceverlo. Condividi le tue opinioni sui problemi di GitHub dell'API Sanitizer e discuti con gli autori delle specifiche e con le persone interessate a questa API.
Se riscontri bug o comportamenti imprevisti nell'implementazione di Chrome, segnala un bug. Seleziona i componenti Blink>SecurityFeature>SanitizerAPI e condividi i dettagli per aiutare gli implementatori a monitorare il problema.
Demo
Per vedere l'API Sanitizer in azione, consulta il playground dell'API Sanitizer di Mike West:
Riferimenti
- Specifica dell'API HTML Sanitizer
- Repository WICG/sanitizer-api
- Domande frequenti sull'API Sanitizer
- Documentazione di riferimento dell'API HTML Sanitizer su MDN
Foto di Towfiqu barbhuiya su Unsplash.