Writeup for Cat Club (Web) - 1337UP LIVE CTF (2024) ๐
Video walkthrough
Challenge Description
People are always complaining that there's not enough cat pictures on the internet.. Something must be done!!
Solution
Players open the website to some random cute cats.
j/k they are my cute cats ๐ฅฐ
We can create an account and login, to view more pics.
Not much interesting to note, except perhaps that our username is reflected back to use. Let's check the downloadable source code.
We'll see a sanitizer.js, which sounds interesting. It prevents us from entering non-alphanumeric characters in the username.
Let's check the code where the username is reflected on the page.
Looks like an SSTI, if we could only enter those dangerous characters ๐ค We should check the getCurrentUser middleware.
So, our username is read from the JWT? Maybe we can tamper with it..
The none algorithm is blocked, so we can't remove the signature verification but how about algorithm confusion? If we can change the token from RS256 (asymmetric) to HS256 (symmetric) and then sign with the public key, the server will use the same key to verify the signature ๐ง
You can do this with the JWT tool, or one of the JWT extension in burp. I made a video series covering the JWT attack material and labs from Portswigger, over on the Intigriti channel if you are interested ๐
The public key is exposed on the common /jwks.json endpoint.
All that's left is to modify our username with a Pug SSTI payload, e.g. from PayloadsAllTheThings
I automated the whole process with detailed comments explaining each step. You just need to update the BASE_URL, JWT_TOOL_PATH and the ATTACKER_SERVER in the SSTI_PAYLOAD.
solve.py
The attacker server will receive a request containing the base64-encoded flag.
function sanitizeUsername(username) {
const usernameRegex = /^[a-zA-Z0-9]+$/;
if (!usernameRegex.test(username)) {
throw new BadRequest("Username can only contain letters and numbers.");
}
return username;
}
router.get("/cats", getCurrentUser, (req, res) => {
if (!req.user) {
return res.redirect("/login?error=Please log in to view the cat gallery");
}
const templatePath = path.join(__dirname, "views", "cats.pug");
fs.readFile(templatePath, "utf8", (err, template) => {
if (err) {
return res.render("cats");
}
if (typeof req.user != "undefined") {
template = template.replace(/guest/g, req.user);
}
const html = pug.render(template, {
filename: templatePath,
user: req.user,
});
res.send(html);
});
});