Il cross-site scripting è una vulnerabilità delle applicazioni web che consente di far eseguire codice non previsto dentro il browser di chi visita una pagina legittima. Il difetto non sta nel browser né nell’utente: sta nell’applicazione, che accetta un contenuto proveniente dall’esterno e lo restituisce nella pagina senza neutralizzarlo.
È una delle vulnerabilità più longeve del web e continua a comparire nelle classifiche OWASP, nonostante le contromisure siano note da vent’anni. La ragione è che le difese funzionano quasi sempre per impostazione predefinita nei framework attuali, e quindi il problema si concentra proprio nei punti in cui quella protezione viene aggirata.

Come nasce la vulnerabilità
Ogni applicazione web riceve dati dall’esterno e li rimanda a schermo: il nome inserito in un modulo, un parametro nell’indirizzo, il contenuto di un commento, il risultato di una ricerca. Il browser che riceve la pagina non ha modo di sapere quale parte del contenuto provenga dagli sviluppatori e quale da un visitatore: esegue ciò che trova.
Il cross-site scripting nasce quando un dato di provenienza esterna arriva nella pagina in una posizione dove il browser lo interpreta come istruzione invece che come testo. Il problema non è l’input in sé, ma il contesto in cui viene restituito: lo stesso contenuto è innocuo dentro un paragrafo e pericoloso dentro un attributo di tag o all’interno di uno script.
Questa è anche la ragione per cui la vulnerabilità sopravvive alle riscritture. Un’applicazione può gestire correttamente novantanove punti di uscita e sbagliarne uno, e quell’uno è sufficiente. Nella classificazione OWASP il cross-site scripting è ricondotto alla famiglia dell’injection, che raccoglie tutte le vulnerabilità in cui un dato viene interpretato come codice.
Le tre tipologie di cross-site scripting
La distinzione riguarda il percorso che il contenuto compie prima di arrivare nel browser, e determina quanto è difficile individuare il problema.
| Tipo | Da dove arriva il contenuto | Chi viene colpito | Difficoltà di rilevamento |
|---|---|---|---|
| Riflesso | Dalla singola richiesta, di norma da un indirizzo costruito ad arte | Solo chi apre quel collegamento, quindi serve indurre la vittima a farlo | Bassa: è la forma che gli strumenti automatici individuano meglio |
| Persistente | Dall’archivio dell’applicazione, dove è stato salvato in precedenza | Chiunque apra quella pagina, senza bisogno di alcun collegamento | Media, e alta se il contenuto è visibile solo a un’area riservata |
| Basato su DOM | Non transita dal server: è il codice lato browser che lo elabora | Chi apre la pagina nelle condizioni previste dall’attacco | Alta: non è visibile nella risposta del server e sfugge alle scansioni classiche |
La forma persistente è la più grave in termini di conseguenze, perché non richiede che nessuno clicchi nulla. La forma basata su DOM è la più insidiosa in termini di individuazione: poiché non lascia traccia nella risposta del server, uno strumento che analizza soltanto ciò che il server restituisce non la vede.
Cosa comporta per un’azienda
L’errore ricorrente nel valutare questa vulnerabilità è considerarla un problema del singolo visitatore. Le conseguenze reali riguardano invece l’organizzazione che gestisce l’applicazione.
Il primo effetto è il furto della sessione. Con un token di sessione un attaccante entra nell’applicazione con l’identità della vittima, senza conoscerne la password e senza incontrare il secondo fattore di autenticazione, perché quel controllo è già stato superato dall’utente legittimo. Se la vittima ha un profilo amministrativo, l’accesso ottenuto è amministrativo.
Il secondo effetto è la modifica di ciò che gli utenti vedono. Un contenuto alterato all’interno di un portale aziendale, di un’area riservata ai clienti o di un sito istituzionale può veicolare richieste di credenziali che appaiono provenire da voi. La firma della vostra azienda è ciò che rende credibile la richiesta, ed è la ragione per cui questa vulnerabilità produce un danno di reputazione superiore al danno tecnico.
Il terzo effetto riguarda i dati. Le applicazioni web attuali eseguono nel browser una parte rilevante della logica, e ciò che è accessibile a quel codice diventa accessibile al codice iniettato: contenuti visualizzati, dati precaricati, informazioni presenti nell’archiviazione locale del browser.
Perché i framework moderni hanno cambiato il quadro
Questa è la parte che distingue la situazione attuale da quella di dieci anni fa. I principali strumenti di sviluppo front-end oggi in uso — React, Angular, Vue — applicano la neutralizzazione dei contenuti per impostazione predefinita: un dato inserito nel modo consueto viene restituito come testo, non come istruzione, senza che lo sviluppatore debba occuparsene.
Il risultato è che nelle applicazioni costruite con questi strumenti la vulnerabilità non si presenta più in modo diffuso, ma si concentra in un numero ridotto di punti specifici. Sono quattro.
Il primo sono le funzioni che i framework stessi mettono a disposizione per inserire contenuto HTML grezzo, aggirando deliberatamente la protezione. Servono quando è necessario mostrare testo formattato, per esempio contenuti provenienti da un editor o da un sistema di gestione dei contenuti, e chi le usa si assume la responsabilità di sanificare il dato prima.
Il secondo sono i valori inseriti in contesti che non sono testo: un indirizzo dentro un collegamento, un parametro dentro un attributo, un dato dentro un blocco di codice eseguibile. La neutralizzazione predefinita agisce sul contesto testuale, e in quelli diversi le regole cambiano.
Il terzo è il codice che manipola direttamente la struttura della pagina lato browser, dove il framework non interviene. È l’origine della forma basata su DOM, e ricorre nelle porzioni di codice più vecchie o scritte fuori dalle convenzioni del framework.
Il quarto sono le librerie di terze parti. Un componente esterno che inserisce contenuto nella pagina introduce i propri difetti, e nessuna revisione del codice proprio li rileva: è la stessa dinamica delle vulnerabilità che risiedono nelle dipendenze esterne anziché nel codice dell’applicazione.
C’è poi la parte di applicazioni che non usa framework moderni. I portali gestionali interni, gli strumenti sviluppati anni fa e mai riscritti, le aree amministrative che nessuno tocca perché funzionano: lì la protezione predefinita non esiste, e la superficie resta quella di dieci anni fa.
Le difese che funzionano
Le contromisure si dispongono su livelli, e la ragione per usarle insieme è che ciascuna copre ciò che le altre lasciano scoperto.
| Misura | Cosa fa | Cosa non copre |
|---|---|---|
| Neutralizzazione in uscita, adeguata al contesto | È la difesa risolutiva: il dato viene restituito come testo e non come istruzione | I punti in cui è stata volutamente aggirata per mostrare contenuto formattato |
| Sanificazione con librerie dedicate | Consente di mostrare testo formattato mantenendo solo i tag ammessi | Nulla, se la libreria è aggiornata; molto, se è stata scritta in casa |
| Content Security Policy | Limita quali script il browser è autorizzato a eseguire, riducendo l’effetto di una falla presente | Non corregge la vulnerabilità, e una policy troppo permissiva non protegge affatto |
| Attributi HttpOnly e SameSite sui cookie | Impediscono che il token di sessione sia leggibile dal codice nella pagina | Le azioni compiute dal codice iniettato con la sessione già aperta |
| Validazione in ingresso | Riduce la superficie scartando in partenza i dati che non rispettano il formato atteso | Non sostituisce la neutralizzazione in uscita, e da sola non è sufficiente |
| Web application firewall | Intercetta i tentativi più comuni e riconoscibili prima che raggiungano l’applicazione | Le varianti costruite per aggirarlo, e la forma basata su DOM, che non transita dal server |
Sull’ultima riga vale una precisazione, perché genera equivoci. Un firewall applicativo è una mitigazione utile e in alcune situazioni indispensabile, per esempio quando una correzione richiede tempo. Non è però una correzione: la vulnerabilità resta nell’applicazione, e la protezione dipende dalla capacità del filtro di riconoscere una forma che non ha ancora visto.
Resta la parte organizzativa. Chi sviluppa deve sapere quali funzioni aggirano la protezione predefinita e in quali condizioni possono essere usate: è materia di formazione tecnica, non di buone intenzioni, ed è l’unico livello che agisce prima che il difetto entri nel codice.
Perché gli scanner automatici non trovano tutto
Gli strumenti automatici individuano bene la forma riflessa, dove il contenuto inviato ricompare nella risposta e la corrispondenza è verificabile in modo meccanico. Sulle altre due forme il rendimento cala, per tre ragioni concrete.
La forma basata su DOM non transita dal server, quindi uno strumento che confronta richieste e risposte non ha nulla da confrontare: serve interpretare il codice eseguito nel browser, che è quello che fa un’analisi dinamica sull’applicazione in esecuzione.
La forma persistente richiede di raggiungere la pagina in cui il contenuto salvato viene mostrato, e non sempre è la stessa in cui è stato inserito. Se la visualizzazione avviene in un’area amministrativa, una scansione senza credenziali si ferma alla pagina di accesso e non arriva mai a vederla.
E resta il contesto. Uno strumento verifica se un valore compare nella pagina, ma stabilire se in quella specifica posizione sia effettivamente sfruttabile richiede una valutazione che l’automatismo non compie: è il motivo per cui i risultati vanno validati, e la ragione per cui un elenco di segnalazioni contiene sempre anche falsi positivi.
