⌨️ Sviluppo web

Prevenire il cross-site scripting (XSS): guida per sviluppatori

Cos'è il cross-site scripting, perché nasce da dati non fidati nel contesto sbagliato e quali pratiche di codifica e sanitizzazione lo prevengono davvero.

Il cross-site scripting (XSS) è una delle vulnerabilità web più diffuse e più longeve. Il meccanismo è semplice: un dato fornito da qualcuno (un commento, un nome utente, un parametro nell'URL) finisce nella pagina e il browser lo interpreta come codice invece che come testo. Da quel momento lo script gira con i privilegi del tuo sito: può leggere i dati mostrati, compiere azioni a nome dell'utente, modificare la pagina.

La buona notizia è che le difese sono note e, se applicate con coerenza, molto efficaci.

Le tre forme di XSS

  • Riflesso: il dato arriva nella richiesta (per esempio un parametro di ricerca) e il server lo rimanda nella pagina di risposta senza trattarlo.
  • Memorizzato: il dato viene salvato (in un database, in un file) e mostrato ad altri utenti in seguito. È il più grave, perché colpisce chiunque visiti la pagina.
  • Basato sul DOM: tutto avviene nel browser. Uno script legge un valore da una fonte controllabile, come location.hash, e lo inserisce nella pagina in modo non sicuro.

La radice è sempre la stessa: dati non fidati inseriti in un contesto in cui vengono interpretati come codice.

Regola numero uno: codificare l'output in base al contesto

Il principio centrale non è "pulire l'input", ma codificare l'output nel modo giusto per il punto in cui viene inserito. Lo stesso dato richiede trattamenti diversi a seconda del contesto:

Contesto Esempio Trattamento
Testo HTML <p>DATO</p> codifica HTML di <, >, &, ", '
Attributo HTML <input value="DATO"> codifica HTML e attributo sempre tra virgolette
URL <a href="DATO"> verifica dello schema e codifica URL
JavaScript <script>var x = "DATO"</script> da evitare: passa i dati in altro modo
CSS style="color: DATO" da evitare o limitare a valori noti

Nella pratica, i sistemi di template moderni codificano automaticamente l'output nel contesto HTML. Il rischio nasce quando questa protezione viene disattivata, di solito con funzioni o filtri dai nomi come raw, safe o unescaped. Ogni loro uso va giustificato e rivisto.

Nel browser: textContent invece di innerHTML

Nel JavaScript lato client, la scelta dell'API fa tutta la differenza:

const nome = new URLSearchParams(location.search).get('nome');

// Pericoloso: il browser interpreta il markup
saluto.innerHTML = 'Ciao ' + nome;

// Sicuro: il valore è trattato come testo puro
saluto.textContent = 'Ciao ' + nome;

Le API da trattare con sospetto quando ricevono dati esterni sono innerHTML, outerHTML, insertAdjacentHTML, document.write e, naturalmente, eval, new Function e setTimeout con una stringa. Per costruire elementi, usa createElement, textContent e setAttribute con attributi non eseguibili.

Anche i framework moderni codificano di default ciò che inserisci nei template. Le loro "porte di uscita" (proprietà che inseriscono HTML grezzo) esistono per casi particolari e vanno usate solo con contenuto sanitizzato.

Attenzione agli URL

Un link il cui indirizzo proviene dall'utente può contenere uno schema javascript:, che esegue codice al clic. Codificare l'HTML non basta: devi verificare lo schema.

function urlSicuro(valore) {
  try {
    const url = new URL(valore, location.origin);
    return ['http:', 'https:', 'mailto:'].includes(url.protocol) ? url.href : '#';
  } catch {
    return '#';
  }
}
link.href = urlSicuro(inputUtente);

Usa un elenco di schemi consentiti, non un elenco di schemi vietati: gli elenchi di divieti si aggirano facilmente.

Quando devi accettare HTML

A volte l'HTML è proprio il contenuto: un editor di testo ricco, un commento formattato, un articolo importato. In questi casi la codifica renderebbe tutto illeggibile, quindi serve la sanitizzazione: un componente che analizza l'HTML e conserva solo elementi e attributi sicuri, eliminando script, gestori di eventi e URL pericolosi.

  • Non scrivere un sanitizzatore con le espressioni regolari: l'HTML ha troppe varianti e casi limite.
  • Usa una libreria mantenuta e diffusa, come DOMPurify nel browser o l'equivalente nel tuo linguaggio lato server.
  • Configura un elenco ristretto di elementi consentiti, adatto al caso d'uso.
  • Sanitizza il più vicino possibile al punto in cui l'HTML viene inserito nella pagina.

Difese aggiuntive

Le misure seguenti non sostituiscono la codifica corretta, ma ne limitano le conseguenze se qualcosa sfugge:

  • Content Security Policy restrittiva, senza 'unsafe-inline': blocca l'esecuzione della maggior parte degli script iniettati.
  • Cookie con attributo HttpOnly: uno script iniettato non può leggerli.
  • Trusted Types: un meccanismo supportato da alcuni browser che, attivato tramite CSP, impedisce di passare stringhe semplici alle API pericolose come innerHTML, obbligando a passare da funzioni di controllo esplicite.
  • Validazione dell'input: rifiutare dati con formato errato (un CAP che contiene lettere, un numero che contiene codice) riduce la superficie, anche se da sola non basta.

Come cercare XSS nel proprio codice

  1. Cerca nel codice tutte le occorrenze di innerHTML, insertAdjacentHTML, document.write, eval e dei filtri che disattivano la codifica nei template.
  2. Per ognuna, chiediti da dove arriva il dato e se può essere influenzato da un utente.
  3. Usa strumenti di analisi statica e linter con regole di sicurezza.
  4. Nei test, inserisci nei campi caratteri come <, > e virgolette e verifica che compaiano come testo nella pagina.

In sintesi

  • L'XSS nasce da dati non fidati interpretati come codice nella pagina.
  • Codifica l'output in base al contesto e non disattivare l'escaping dei template senza motivo.
  • Nel browser preferisci textContent e createElement a innerHTML.
  • Verifica lo schema degli URL con un elenco di valori consentiti.
  • Se devi accettare HTML, sanitizzalo con una libreria affidabile e affianca una CSP restrittiva.
MM
Marco Marzaduri

Marco Marzaduri si occupa di cybersecurity, informatica forense e sviluppo web. Laureato in informatica, unisce il rigore dell'analisi tecnica all'attenzione per le persone che usano la tecnologia.

Leggi anche