Template engines are widely used in web applications to combine HTML and data to generate displays. For example, template engines are used in the backend to embed usernames, post contents, etc. into HTML.
user input values are directly included in such template processing , the entire template syntax may be evaluated. This is a vulnerability known as "SSTI (Server-Side Template Injection)."
If an SSTI occurs, depending on the type of template engine, there is a risk that it could lead to internal variable access or, in the worst case, arbitrary OS command execution (RCE). In particular, in Python-based systems, Jinja2
and Mako
have a vulnerable implementation, they can become a convenient "entrance" for attackers.
Mako
the subject of this verification , is one of the template engines commonly used in Python, but it is also prone to accidentally embedding user input directly
We will look at a concrete example to see how this vulnerability can be exploited in a real application.
📹: You can also see how it's done in practice on YouTube!
- The crisp typing feel that is unique to the capacitive non-contact system!
- REALFORCE's first wireless compatible device! Wired connection also available!
- Unlike the HHKB, the Japanese keyboard layout has no quirks and is easy for anyone to use!
- Equipped with a thumb wheel, horizontal scrolling is very easy!
- It also has excellent noise reduction performance, making it quiet and comfortable!
- Scrolling can be switched between high speed mode and ratchet mode!
About HackTheBox
This time, we are actually HackTheBox (HTB) to verify vulnerabilities.
HackTheBox is a hands-on CTF platform where participants can practice in a variety of security fields, including web applications, servers, and networks.
Its greatest feature is that participants can learn by actually accessing the machines and applications that will be attacked and getting their hands dirty.
is one of the Challenge categories that was previously offered on HackTheBox , and is currently only accessible to users with a VIP plan or higher
There are also machines and challenges in a variety of categories, including Web, Reversing, Pwn, and Forensics, so you can tackle them at a level that suits you.
If you want to seriously hone your skills with HackTheBox, be sure the VIP plan and take full advantage of past machines and challenges.
👉 Visit the official HackTheBox website here
👉 For detailed information on how to register for HackTheBox and the differences between plans, please click here.

Challenge Summary: Spookifier
The challenge we tackled this time, " Spookifier is classified in HackTheBox's Web category is set to VERY EASY
On the surface, the application is very simple: a web service that simply converts text you enter into different font styles and displays them. At first glance, it doesn't appear to have any security vulnerabilities.
However, the application the Python template engine "Mako," and the string entered by the user was evaluated directly within the template.
This design flaw can lead to Server-Side Template Injection (SSTI) and ultimately arbitrary command execution (RCE)
👉 https://app.hackthebox.com/challenges/Spookifier
Challenge Points
- Technologies used: Python, Flask, Mako
- Notable behavior: Input is displayed in multiple fonts
- Attack vector: Unsanitized input in template → SSTI
- Target: Retrieve flags from
/flag.txt
In this way, behind the functionality that appears safe on the surface a weakness in the template evaluation process , and the structure allows you to experience a typical ``pattern from SSTI to RCE.''
Hacking in practice: From SSTI to RCE
From here, we will actually play around with the app and look for loopholes.
If the template is handled carelessly, it could become an entry point for an attack.
We will verify whether SSTI is valid and ultimately aim for RCE.
Scouting 1: First, try out the app
First, access the target application and check what functions it has and what input it accepts
The screen has a simple input form, and it appears to have only the function of "entering text and converting it to a decorated font style and outputting it."
The appearance and structure are very simple, with no authentication functions or complex routing.

The important thing to note here is the string entered by the user is processed in some way and then displayed as a template .
It seems that somewhere in the process of "displaying in multiple fonts" the string is being manipulated by a template engine.

I checked the request information using devtools, but there doesn't seem to be any significant information there.

Reconnaissance 2: Identifying the template engine from the source code
When investigating vulnerabilities in actual web applications, it is rare to have access to the source code.
Penetration testing and bug bounty testing rely on black box testing (observing behavior from the outside).
Therefore, we usually check for the presence of a template engine and whether there are any vulnerabilities through the following process.
- In the text field, try out some common template syntax such as
${7*7}
,{{7*7}}
,<%= 7*7 %>
- the result shows
49
or7*7
- Any unusual template errors or evaluation results can provide clues to the template engine.
- Infer the template engine used from its output and behavior (Mako, Jinja2, Twig, etc.)
This time, the source code was provided as a special challenge aimed at learning about vulnerabilities,
allowing me to directly examine the internal structure and quickly understand how template processing works.
In the provided source code, you can find the following template rendering description:
from mako.template import Template ... return Template(result).render()
This sentence clearly shows that
Mako is being used as the template engine Mako is a Python template engine, and the ${...}
syntax is evaluated directly as a Python expression, which poses a risk of SSTI (Server-Side Template Injection)
will try to evaluate ${}
in user input
Now that we know that Mako is used as the template engine, the next thing to check is
the ${}
in the string entered by the user is actually evaluated as a template expression , which is the first step in determining whether SSTI (Server-Side Template Injection) exists.
try entering Mako's basic template syntax, ${7*7},
${7*7}
If the template engine evaluates this input as an expression, it should output the number
49.
Conversely, if it is not evaluated and treated as a string, it will simply be displayed on the screen as ${7*7}
When I sent it, the following message was displayed on the screen:

The result 49
the ${7*7}
entered by the user has been evaluated by the template engine (Mako).
In other words, Mako processes user input as a template , and it was confirmed that SSTI was in effect.
Intrusion: Check for RCE ⇒ Try to see how far you can execute it from the template formula
With SSTI confirmed, our next goal was clear:
to see if we could leverage template expressions to execute arbitrary code on the server side (RCE)
Confirmation of command execution
First, let's test whether we can actually execute an OS command from a template expression.
To check, we will use the simple whoami
command.
${__import__('os').popen('whoami').read()}
When I submitted this formula, I got the following result:

The execution result of whoami
shows root
which confirms that any OS command can be executed through a template expression , and that the command is executed with root privileges
At this point, it is clear that a remote code execution (RCE) vulnerability exists, allowing an attacker to execute commands with elevated privileges
Checking file read
Now that we know we can execute commands, we can check
whether we can read any file A typical example /etc/passwd
file, which is familiar in Linux environments.
${open('/etc/passwd').read()}
The output included the user information, such as:

This result shows that
it is possible to call Python's open()
, giving you full control over read operations on the server.
Execution: Search and get flags
So far, through template injection
- Running Python Code
- Execute OS commands
- Arbitrary file reading
This means that a complete remote code execution (RCE) has been achieved .
The next step to find the target flag file and retrieve its contents .
Find the location of the flag file
First, let's look at the contents of the root directory /
and see what directories and files are there.
${__import__('os').listdir('/')}

As you can see, it's fine if you can find the desired file ( flag.txt
), but if you can't find it, you'll need to dig deeper into home
or root
Reading the flag
If the search finds a file such as /flag.txt
open()
to retrieve its contents:
${open('/flag.txt').read()}
When I ran this expression, I got the following output:

We successfully obtained
the flag, which was the final goal of the HackTheBox challenge Through this process, we were able to gain a deeper understanding of the extent to which the SSTI vulnerability can be exploited through actual testing.
Why did this vulnerability occur? How to use templates safely
The main cause of this vulnerability (SSTI → RCE) the inappropriate use of the template engine
In particular, the Python side dynamically constructed template strings containing user input , leading to template injection (SSTI) and eventual remote code execution (RCE).
Solution – Don’t pass dynamic strings to templates
One of the typical patterns in which SSTI (Server-Side Template Injection) occurs
when a string is constructed in Python and then evaluated as a template .
Creating template strings using str.format()
, as in the code below,
def generate_render(converted_fonts): result = '''<tr><td> {0}</td></tr><tr><td> {1}</td></tr><tr><td> {2}</td></tr><tr><td> {3}</td></tr> '''.format(*converted_fonts) return Template(result).render()
For example, converted_fonts
contains the following user input:
${__import__('os').popen('id').read()}
Passing this template to Mako creates a serious vulnerability Mako will evaluate ${...}
Safe usage: Separating template structure from data
To prevent this issue, it's important
to separate the template structure from the user input (variables) You can safely handle this by explicitly passing variables to render()
, like this:
def generate_render(converted_fonts): template = '''<tr><td> ${font1}</td></tr><tr><td> ${font2}</td></tr><tr><td> ${font3}</td></tr><tr><td> ${font4}</td></tr> ''' return Template(template).render( font1=converted_fonts[0], font2=converted_fonts[1], font3=converted_fonts[2], font4=converted_fonts[3] )
With this method, font1
through font4
contain ${}
, Mako will treat them as ordinary strings and will not evaluate them as template expressions.

Mako's ${...}
syntax evaluates expressions within templates,
and if user input is passed in verbatim, there is a risk of code execution.
On the other hand, if you pass variables explicitly using
Template(...).render(var=value)
In this case, var }
can safely be used as a placeholder in the template , but will not be executed as code.
Summary: SSTIs are vulnerabilities caused by "usage habits"
In this "Spookifier" challenge, improper handling of Mako templates
allowed us to exploit a vulnerability that led to Server-Side Template Injection (SSTI) and ultimately to Remote Code Execution (RCE).
These vulnerabilities are caused by
implementation mistakes made by developers They are particularly dangerous when template strings are dynamically constructed.
To use template engines safely:
- Always separate templates and data (variables)
- Pass variables as placeholders and do not evaluate expressions
- Do not pass untrusted input directly to templates
By simply following these basic principles, many SSTIs can be prevented.
👉 For detailed information on how to register for HackTheBox and the differences between plans, please click here.
