Il Cross-Site Scripting (XSS) è la vulnerabilità web più diffusa al mondo — e una delle più pericolose. Permette a un attaccante di eseguire codice JavaScript arbitrario nel browser della vittima, rubare sessioni, modificare contenuti e reindirizzare utenti. Vediamo come proteggersi.
I tre tipi di XSS
1. XSS riflesso (Reflected XSS)
Lo script malevolo è inserito nell'URL o in un parametro della richiesta e viene "riflesso" nella risposta del server.
Vettore tipico: L'attaccante invia un link contenente codice malevolo via email o social media. Se il server inserisce il parametro direttamente nell'HTML senza sanitizzare, lo script viene eseguito nel browser della vittima.
2. XSS persistente (Stored XSS)
Lo script viene salvato nel database (commento, profilo, messaggio) e mostrato a tutti gli utenti che visualizzano la pagina. Molto più pericoloso del riflesso perché colpisce automaticamente tutti i visitatori.
3. XSS basato su DOM (DOM-based XSS)
Lo script viene eseguito manipolando il DOM direttamente nel browser, senza passare dal server. Avviene quando il codice JavaScript lato client legge dati dall'URL e li inserisce nel DOM senza sanitizzazione.
Le difese fondamentali
1. Output encoding (prima difesa)
Codificate ogni dato dinamico prima di inserirlo nell'HTML. Il tipo di encoding dipende dal contesto:
| Contesto | Encoding necessario | Esempio |
|----------|-------------------|---------|
| Contenuto HTML | HTML entity encoding | < diventa < |
| Attributi HTML | Attribute encoding | " diventa " |
| JavaScript | JavaScript encoding | ' diventa \x27 |
| URL | URL encoding | < diventa %3C |
| CSS | CSS encoding | Escape con backslash |
Errore comune: usare HTML encoding in un contesto JavaScript. Ogni contesto richiede il proprio encoding.
2. Content Security Policy (CSP)
La CSP è il secondo livello di difesa. Anche se un XSS riesce a iniettare uno script, la CSP può bloccarne l'esecuzione.
Content-Security-Policy: script-src 'self' 'nonce-abc123'
Con questa policy, solo gli script dal vostro dominio o con il nonce corretto vengono eseguiti.
3. Sanitizzazione dell'input
Per i contenuti che devono contenere HTML (editor rich text, markdown), usate una libreria di sanitizzazione come DOMPurify:
import DOMPurify from 'dompurify'
// Sanitizza mantenendo solo i tag HTML sicuri
const cleanHTML = DOMPurify.sanitize(userHTML, {
ALLOWED_TAGS: ['p', 'b', 'i', 'a', 'ul', 'ol', 'li', 'br'],
ALLOWED_ATTR: ['href', 'title'],
})
Non inserite MAI HTML non sanitizzato nel DOM. Usate sempre una libreria dedicata.
4. Cookie HttpOnly
I cookie con flag HttpOnly non sono accessibili via JavaScript:
Set-Cookie: session=abc123; HttpOnly; Secure; SameSite=Strict
Anche se un attaccante esegue uno script XSS, non potrà leggere il cookie di sessione.
5. Validazione dell'input
Validazione rigorosa lato server su tutti gli input:
import { z } from 'zod'
const commentoSchema = z.object({
testo: z.string()
.min(1)
.max(5000),
autore: z.string().min(1).max(100),
})
Framework e protezioni automatiche
React
React codifica automaticamente tutto ciò che viene renderizzato con {}. Le vulnerabilità XSS in React derivano quasi sempre dall'uso di API che bypassano la sanitizzazione automatica (come l'inserimento diretto di HTML nel DOM) o da href con protocollo javascript:.
Regola: non inserite mai HTML non sanitizzato nel DOM, anche se il framework lo permette.
Vue.js
Vue codifica automaticamente con {{ }}. Vulnerabilità con v-html che bypassa la protezione. Stessa regola: sanitizzare sempre con DOMPurify prima di inserire HTML utente.
Angular
Angular sanitizza automaticamente. Le vulnerabilità emergono quando si bypassa esplicitamente la sanitizzazione. Usate le API di bypass solo con contenuti fidati e sanitizzati.
Difesa in profondità: schema completo
INPUT PROCESSING OUTPUT
│ │ │
├─ Validazione Zod ├─ Logica business ├─ Output encoding
├─ Tipo checking ├─ Sanitizzazione HTML ├─ CSP header
├─ Lunghezza max │ (DOMPurify) ├─ Cookie HttpOnly
└─ Pattern matching └─ Parametri SQL └─ SameSite cookie
Ogni livello è indipendente: se uno fallisce, gli altri proteggono ancora.
Testing per XSS
Payload di test comuni
Testate ogni punto in cui l'input utente viene visualizzato:
<img src=x onerror=alert(1)>
<svg onload=alert(1)>
"><img src=x onerror=alert(1)>
Dove testare
- Campi di ricerca
- Commenti e recensioni
- Profili utente (nome, bio)
- URL e parametri query
- Header HTTP (Referer, User-Agent)
- Upload di file (nome file, metadati)
- Form di contatto e messaggi
Strumenti automatizzati
| Strumento | Tipo | Uso | |-----------|------|-----| | OWASP ZAP | Scanner attivo | Test automatizzati completi | | Burp Suite | Proxy + scanner | Analisi manuale + automatica | | WarDek Scanner | Scansione passiva | Verifica header e configurazioni |
Checklist anti-XSS
- [ ] Output encoding appropriato al contesto su tutti i dati dinamici
- [ ] Content Security Policy implementata con nonce o hash
- [ ] Cookie di sessione con flag HttpOnly
- [ ] DOMPurify per contenuti HTML generati dagli utenti
- [ ] Nessun inserimento diretto di HTML non sanitizzato nel DOM
- [ ] Validazione input lato server con Zod o equivalente
- [ ] Test XSS automatizzati nella pipeline CI/CD
- [ ] Audit di sicurezza periodico
- [ ] Formazione sviluppatori sulle pratiche anti-XSS
Conclusione
La protezione XSS è un esercizio di difesa in profondità: encoding dell'output, CSP, cookie HttpOnly, sanitizzazione e validazione. Nessuna singola misura è sufficiente, ma l'insieme rende gli attacchi XSS estremamente difficili da sfruttare.
Scansiona il tuo sito web gratis con WarDek — OWASP, NIS2, GDPR, AI Act in un'unica scansione.