SSTI 1 – picoCTF Walkthrough
Challenge: Server-Side Template Injection (SSTI)
Description:
I made a cool website where you can announce whatever you want! Try it out!
I heard templating is a cool and modular way to build web apps!
Hint:
Server Side Template Injection
What is SSTI?
Sever-Side Template Injection (SSTI) is a vulnerability where user input is directly rendered in a server-side template, allowing attackers to inject and execute code on the server.
If the template engine doesn’t sanitize user input, things like this become dangerous: Hello
If name is user-controlled, the attacker could inject: `` Which renders:Hello 49
Now imagine instead of math, the attacker injects Python code to run commans or read files.
What the Site Looked Like
The site was simple with a text box beside the question “What do you want to announce?”
Whatever you typed was reflected on the page. Classic reflection = test for SSTI
Step 1: Testing the Template Engine
I started small by typing: ``
The site returned 49
This meant that it was vulnerable to SSTI, and likely using the jinja2 template engine (used by Flask and many Python apps)
Step 2: Escalating with Object Traversal
To gain deeper access, I use this payload: ``
What does this do?
- config: A Flask object in Jinja2’s environment.
.__class__.__init__.__global__
: Gives access to Python’s global variable from the class constructor.popen('ls').read()
: Run a shell command and returns the output
In short: this breaks out of the sandbox and let you run system commands.
Step 3: Viewing the Files
The output of the above command showed:
1
2
3
app.py
templates/
flag requirements.txt
flag requirement.txt is almost always what we need in a CTF
Step 4: Reading the Flag
To the read the flag, the following was ran ``
The following flag was printed: picoCTF{s4rv3r_s1d3_t3mp14t3_1nj3ct10n5_4r3_c001_ae48ad61}
What I learned
- SSTI can be powerful and dangerous - from math to full remote code execution.
- Knowing the internals of Jinja2 (or any engine) is helpful in crafting payloads.
- Object traversal like
config.__class__.__init__.__globals__
is a key way to break out of templates.
Other Templates Engines to Watch Out For
| Engine | Language | Syntax Example | Known for SSTI? | |————-|———-|—————-|——————| | Jinja2 | Python | | Yes | | Twig | PHP |
| Yes | | Smarty | PHP | {$...}
| Yes | | Velocity | Java | $variable
| Yes | | Freemarker | Java | ${...}
| Yes | | EJS | Node.js | <%= ... %>
| Yes | | Razor | .NET | @{...}
| Yes (rare) |
Always test different expressions like , ${77}, <%= 77 %> to see what sticks.
How to Prevent SSTI
Developers: Here’s how to stay safe:
- Don’t use render_template_string() with raw user input.
- Always sanitize or escape user input in templates.
- Use whitelisted template variables.
- Avoid evaluating user-controlled strings in your rendering logic.
- Use template engine settings to restrict dangerous features.
Payload Cheat Sheet
| Purpose | Payload | |—————-|————————————————————————-| | Confirm SSTI | | | Access config |
| | List files | | | Read flag.txt |
|
Summary
This was a classic SSTI challenge — starting with a simple reflected input, moving to evaluating math, then escalating to reading the server’s filesystem. Understanding Python’s object model and Jinja2 internals gave me full control.