Month: August 2025

Capture the Flag

Hack the Box Walkthrough: Spookifier

Diet Coke Ghost to represent the Spookifier roomToday, we’re going to take on a Hack the Box challenge called Spookifier. This is a free, retired challenge that you can find here. Unlike the other HackTheBox stuff that I’ve been doing, this won’t be a “Sherlock” / defensive / Blue Team exercise, but rather more of an offensive security exercise where we will actually attempt to exploit an application.

Here’s their description of the challenge, “There’s a new trend of an application that generates a spooky name for you. Users of that application later discovered that their real names were also magically changed, causing havoc in their life. Could you help bring down this application?”

So, there is a machine we have to start and also a zip to download. The password for the zip is hackthebox. Inside, we find the code for the application that is running on the server, so that means that this is a “white box” testing scenario. Instead of just probing the machine to try to guess what it is doing, we can see the source code and take our shot at it. Once you unzip the file, you’ll have a structure like this (shown in Visual Studio Code)

Spookifier Zip File Contents

As I poked around, it looks like this is a pretty simple Python web application that takes what you enter and then displays it with some weird, spooky fonts. What I notice right away (and you can see it in my screenshot above) is that it uses the Mako templating engine, as those imports are present in main.py and util.py. When you’re working a CTF and you know you can enter data that will be processed and you have server side templates, you want to think Server-Side Template Injection (SSTI) pretty early in the things you try. Let’s do that first since this is a challenge marked “Very Easy”, so there aren’t usually rabbit holes at this level.

I started the instance and was given this IP:PORT – 94.237.60.55:57424. After I connected my Kali machine to the HTB VPN, I navigated to the URL and found this

Spookifier Home Page

The very first thing for me is to see how it is supposed to work, the “happy path” if you will. So, I entered Pete and this is what I got. This screenshot may be a little small, but my text I entered also ended up in the URL as http://94.237.60.55:57424/?text=Pete. That’s something else to note if the text input ever gets funny about what we want to enter, we can attack the URL, if necessary.

Spookifier Name Happy Path

So, if we consult PayloadsAllTheThings, we can find how to confirm this is the templating engine in use by looking at the Inject Template Syntax section. The chart there is helpful if you think you have SSTI but don’t already know the engine to slowly work your way through. We are already 99.99% sure, but I’m going to do an initial, simple, first-level check of ${7*7} just to see if we have SSTI. We see 49, so we know we have SSTI.

Spookifier SSTI Check One

Again referencing PayloadsAllTheThings, the final check to confirm Mako is this: ${"z".join("ab")}. If it returns azb, we’re good to go with Mako. You can see here that that was confirmed.

Spookifier SSTI Mako Confirm

Okay, we have SSTI, we have Mako, how can we get command execution? Referencing this site, let’s take a shot at what they suggest.


${self.module.cache.util.os.system("id")}

That didn’t work for me. It returned 0 at the bottom. I’m guessing it is because it executed a command (helpful for reverse shell?), but didn’t return output to the screen. But right above it also says we can do something like this also.


<%
import os
x=os.popen('id').read()
%>
${x}

That popen (process open) and read look tasty, so once we are at os, we want to try that tactic, so let me work that a little differently.


${self.module.cache.util.os.popen("id").read()}

And if I put that, I get this. Successful code execution.
Spookifier SSTI Code Execution

So, when I run this command, I can find out what directory we’re in.


${self.module.cache.util.os.popen("pwd").read()}

That gives us /app. When I run passing in ls, I get


application run.py

If we look at the code we were given in the zip file, the Flag is one level up from where we are. Let’s read it by passing in this command to read a file called flag.txt one level up from our folder.


${self.module.cache.util.os.popen("cat ./../flag.txt").read()}

And that gives us the flag and completes the challenge!

Spookifier Flag

Spookifier Pwned