Intro
Although I really should have been working on final projects as this semester comes to a close, HackTheBox ran their “Cyber Santa is Coming to Town” CTF for the last five days. Since school was getting a little demanding, I was not able to spend as much time on the CTF as I would have liked, but there were a few challenges that I’d like to highlight here on this blog because they were interesting.
“Toy Workshop” was the web challenge released on day 1 and showed off an interesting way to do a classic XSS attack. We’ll start by finding a simple web app where we can forward messages to the “manager” of the workshop. Looking at the source code, we see that the queries are stored in a SQL database, and viewed using the “puppeteer” module, which creates a browser instance to view the database(probably to emulate the “manager”). We can leverage this by writing a XSS payload to take the cookie stored in the browser, and send it to a simple webserver using ngrok to tunnel the traffic to our VM.
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?
Understanding the Web App
Initial Behavior
Just throwing the address we’re given into the search bar gives us this:
There’s a lot of fancy javascript animation going on, and honestly I could probably watch the “transformation portal” (or whatever it is) for a while. The only functionality that is accessible is when we click on an elf. We’re told that the manager is busy, but the message will be forwarded. I can type in whatever I want, and get a “Your message is delivered successfully!” in response.
There isn’t really anything left to do here unless we want to admire the animation some more.
Code Review
The source code we’re given, excluding the /static
directory, looks like this.
kali@transistor:~/ctf/santa_htb/day1/web_toy_workshop$ tree
.
├── build-docker.sh
├── challenge
│ ├── bot.js
│ ├── database.js
│ ├── index.js
│ ├── package.json
│ ├── routes
│ │ └── index.js
│ ├── static
│ │ ├── ...[trim]...
│ └── views
│ ├── index.hbs
│ └── queries.hbs
├── config
│ └── supervisord.conf
└── Dockerfile
It’s a pretty sparse web app. Interestingly, we don’t have a “flag” file. Aside from that, we define some routes, a database, a bot, and the web page itself, which isn’t a lot. Let’s follow the user input through the web app, starting with the routes.
We see that there are 3 different endpoints, but only two are accessible to us.
- The
/
will show us what we originally saw in “Initial Behavior”. - The
/api/submit
handles the requests we were sending, and adds it to the database of requests. The “bot” (again, probably the simulated manager), will then read those queries, and tell us if we’re successful. - The
/queries
is only accessible from the localhost, and will render out a list of the queries from the database.
Since there’s no immediate way to get to the /queries
, we can try looking at the database. These are the relevant functions.
We have some prepared statements to insert values into the SQL database, and to return values. This means we’re not really going to be able to do a SQL injection, as prepared statements handle the query and the user input separately, effectively preventing any SQL injections. Recall that after this, the “bot”, reads the queries.
This is where it all comes together, for a couple of reasons.
- We’ve found where the flag is, in the cookies of the browser that this script opens
- When
readQueries()
is called, it will (1) access that endpoint that we can’t, and (2) display all of the queries that we’ve input on to the page - The database will also reset everytime we send a message, meaning we only need one input
Grabbing the Flag
Strategy
Grabbing another user’s cookie is not unheard of. It’s possible with a number of attacks, depending on the context, but it most commonly revolves around Cross-Site Scripting (XSS). As defined by PortSwigger:
“Cross-site scripting works by manipulating a vulnerable web site so that it returns malicious JavaScript to users. When the malicious code executes inside a victim’s browser, the attacker can fully compromise their interaction with the application.”
Essentially, since the /queries
page renders the queries we’ve submitted, we’re free to also insert HTML tags like <h1></h1>
, since the prepared statement likely isn’t filtering those characters. The important tag here is <script></script>
, which will execute whatever javascript we want, which we can use to fetch the cookie. The next question is how we get the cookie using javascript. Luckily, the payload is pretty simple:
This payload will send a request to a website that I control, and stick the cookie (encoded in base64 using the btoa()
function) in the url. The website doesn’t have to have any functionality; as long as I log the request, I’ll be able to decode the cookie and get the flag.
Setup and Attack
Since I’m working out of a VM, I’ll use ngrok
to tunnel traffic to my Kali machine.
While I could use netcat to catch the request, I like using this Python script to spin up a webserver where I can see the full GET request, headers and all. We also used this in H@cktivitycon’s OPA Secrets challenge.
On the website, I’ll submit the payload from earlier, replacing hacker.thm
for the ngrok
instance that I have, and replacing https
with http
because setting up https
is too much work for a CTF.
After a few seconds, I get a request on my Python server.
Decoding the base64, we get the flag.