Party Cat0%

StylishBossOfficialWriteup|CATCTF25

Omar Mohamed
Thanks for sharing!

بِسْمِ اللَّهِ الرَّحْمَنِ الرَّحِيمِ

Stylish Boss Official Writeup | CAT CTF 25
Welcome welcome welcome! Today I have a special write up for you, the official write up for the Stylish Boss challenge from CAT CTF 25.
Which is my first challenge that I made for a Live CTF, and I am very excited to share it with you!
You can download the challenge files from here and give it a try before reading.
Let's get started!

Challenge Description

Stylish Boss

First Look

The challenge is a web application where users can customize the page font. It also includes a 'Metadata Stripper API', restricted to the Boss role. A report feature lets you submit your profile, causing the Boss to view it with your chosen font settings.
Profile
Font Customizer
That's all from UI. Now let's dive into our code

Code Analysis

In auth.js, the Register and Login functionality are pretty standard, there is just an ApiKey generated randomly for each user upon registration.

In main.js, the / and /profile endpoints render the home and profile pages. Both apply the user’s saved font preference from the database, and /profile additionally embeds the user’s API key.
Since the /profile route renders the profile view, we inspect views/profile.ejs and find the following noteworthy code:
The value of <%- userFont %> is inserted directly into the font-family property without sanitization, which makes it vulnerable to CSS Injection. Additionally, the API key is rendered inside a <div> via the data-internal-api-key attribute.
In EJS, <%- %> outputs raw, unescaped content, unlike <%= %> which escapes HTML characters and would have prevented this issue.
Next we have /set-font endpoint which allows users to set their font, which is then stored in the database.
The font is sanitized by removing < and > characters to prevent HTML injection .
The other endpoints: /report and /boss/view-theme are simple logic to make the Boss view his own profile with the font of the user who reported.

Third file api.js has the following code:
The middleware apiKeyAuth checks if the request has an X-API-Key header and verifies it against the database to ensure the user is a Boss. If the API key is valid, it allows access to the /strip-metadata endpoint.
The objective is to steal the Boss API key, then use it to access the /strip-metadata endpoint and ultimately retrieve the flag. To start, we’ll focus on leaking the Boss’s API key through the CSS Injection vulnerability.

Exploitation: CSS Injection

For background on exploiting CSS Injection to exfiltrate data, this article is a great reference: How you can steal private data through CSS injection.
We know the API key is in the data-internal-api-key attribute of the <div id="api-key-container"> inside views/profile.ejs.
In CSS, certain properties can fetch external resources, such as:
This makes it possible to exfiltrate data through requests. For example:
Here, whenever a <div> exists, the browser will try to load an image from our server.
To make this conditional, we can use CSS attribute selectors. For instance:
This triggers only if the element has the data-internal-api-key attribute. Even better, the ^= operator lets us check prefixes:
That means the browser will only send a request if the API key starts with sk.
Using this trick, we can leak the key character by character. For example:
If the key starts with sk_a, our server gets a request. If not, nothing happens. By iterating over possibilities (sk_aa, sk_ab, …) we can brute-force the full API key.
A working injection payload might look like this:
Here we break out of the font-family, inject our CSS, and close cleanly with a comment.
Instead of guessing one by one, we can test multiple prefixes at once:
Whichever rule matches causes the request, revealing the correct character.
You can first test this on your own profile, then trigger the Boss to load your malicious font via the report feature. This way, his profile page leaks his API key straight to your server.
And just like that.. you’ve got the Boss’s API key.

Code Analysis

Now that we have the Boss API key, we can use it to access the /strip-metadata endpoint.
The /strip-metadata endpoint expects an X-File-Name header containing the file name. It then calls the MetadataStripper class to process the file and return its output.
At first glance, there doesn’t seem to be much room for exploitation. However, since the MetadataStripper class comes from the metadata-stripper npm package, and our input is passed directly into its processFile method, it’s worth inspecting the package code itself on npmjs.com.
Metadata Stripper
We can quickly spot a potential command injection in the package, but there’s some sanitization in place that tries to block it:
  • The first regex strips anything that isn’t a word character, space, or one of these: ${}:()" |*.
  • The second regex removes any occurrence of the substring flag (case-insensitive).
So, what’s left?
  • Allowed: a-zA-Z0-9_, spaces, $ { } : ( ) " | *
  • Blocked: f, l, a, g
The executed command is:

Exploitation: Command Injection

Our task: escape this command and inject our own. Luckily, the pipe operator | is allowed, which means we can chain another command.
From the Dockerfile we know the flag is stored in /flag. Normally, we’d use cat, less, or head, but all of those has blocked characters. However, more is still available! Perfect for reading files.
So a basic injection would be:
But there’s a catch: we can’t directly type flag (f, l, a, g are removed), and / is also disallowed.
Wildcards * are allowed, so * could match the filename, but more * will only do it in the current directory, not /flag.
Here’s where a neat Linux trick comes in: parameter expansion. In Bash, we can grab parts of environment variables. For example:
This returns /, since the first character of $HOME is /. And because ${} syntax is allowed, we can use it to build the path.
Final payload:
Which resolves to:
Flag
CATF{Y0U_M4D3_1T_B055_Y0U_D3S3RV3_4_MU5HR00M_45_4_G1FT!}
That was it! You have successfully exploited the Stylish Boss challenge and obtained the flag!
IMPORTANT: Don't you dare miss out my other challenge "Underground Maze" which I made with my awesome friend Korea: Click here. Thank You <3

Conclusion

This was a fun challenge to make and I hope you enjoyed it as much as I did. It was a great opportunity to be a part of CAT CTF 25 and to create a challenge that is both challenging and educational. I am looking forward to your feedback and suggestions for future challenges. Thank you for reading my write up and I hope you learned something new today!

You might also like