Server-Side Template Injection (SSTI) — Explained Simply

8 minute read

Quick and hands-on walkthrough of an SSTI (Server-Side Template Injection) vulnerability using a PicoCTF challenge. No boring theory dumps — just straight to the point with payloads, screenshots, and flag hunting. If you’re into web exploitation or just starting out with CTFs, this one’s for you.


What’s SSTI?

Server-Side Template Injection (SSTI) is a vulnerability that pops up when user input gets directly embedded into a server-side template engine without proper checks. If this happens, an attacker can inject their own code and get it executed on the server — leading to serious stuff like remote code execution (RCE), data leaks, or even full access to the system.


How Does SSTI Happen?

Web frameworks often use template engines to generate dynamic HTML pages. These templates can include variables like {{ user }} , which the engine replaces with actual values. Common engines are:

  • Jinja2 (Python/Flask)
  • Twig (PHP)
  • Freemarker (Java)
  • Pug (Node.js)

Now, if a developer takes raw user input and sticks it straight into a template without escaping or validating it, it’s game over. Example with Flask + Jinja2:

from flask import Flask, render_template_string, request

app = Flask(__name__)

@app.route("/")
def index():
    user_input = request.args.get("name", "Guest")
    return render_template_string("Hello ")

If someone sends {{7*7}} as input, the output will be: Hello 49 So yeah, the server actually runs that code instead of just showing it as plain text. That’s the whole issue.


Why Does SSTI Even Exist?

It mostly boils down to:

  1. Template engines are designed to evaluate stuff like {{ something }}.
  2. If user input ends up there unchecked, it gets interpreted.
  3. Developers often don’t realize how powerful (and risky) these engines are. If attackers find a way in, they can run serious payloads. Like this one in Jinja2:
 {{ config.__class__.__init__.__globals__['os'].popen('whoami').read() }} 

This runs the whoami command on the server. Yep, it’s that dangerous.


SSTI in Action (PicoCTF Challenge)

Let’s take a quick hands-on look at SSTI using a beginner-friendly challenge from PicoCTF — because honestly, too much theory sucks. Here’s the link to the challenge:

picoCTF - picoGym Challenges

So the setup is simple — there’s a website that just displays whatever you type in a textbox. Like this: Text Box UI

If I type hello, it shows… well, hello. Nothing fancy. Hello Input Hello Output

Now, let’s toss in a classic SSTI payload and see what happens. Try this: {{7*7}}

If the site is vulnerable, the template engine will evaluate7*7 and show 49 instead of just printing the curly braces.

SSTI Payload Input Output is 49

That confirms it — SSTI confirmed

Getting the Flag

Now we know it’s vulnerable, let’s try to poke around and get that flag. We’ll use this payload to list files in the server directory:

{{ config.__class__.__init__.__globals__['os'].popen('ls').read() }}

Output:

__pycache__ app.py flag requirements.txt

There it is! We found the flag file.

Let’s read it:

{{ config.__class__.__init__.__globals__['os'].popen('cat flag').read() }}

And just like that, the flag is ours.


Note:

Not all SSTI challenges will be this straightforward. Sometimes, input sanitization or filters might block basic payloads, so you’ll need to get creative with your bypass techniques.


Some Useful Payload Lists:


How to Prevent SSTI

Here’s what you should do to stay safe:

  1. Don’t use render_template_string() with user input
    – Stick to render_template() with proper, pre-written templates.
  2. Escape and sanitize input
    – Use filters like {{ user_input | e }} to prevent it from being interpreted.
  3. Limit what the template engine can do
    – In Jinja2, go for sandboxing to block dangerous functions.
  4. Use allowlists
    – Only allow expected values. Don’t blindly trust user input.
  5. Add a WAF (Web Application Firewall)
    – It can help block suspicious inputs and common attack patterns.

Wrapping Up

SSTI isn’t just a cool bug — it can fully compromise a system. Knowing how these template engines work and locking down any input that ends up in them is key. Always validate, escape, and play it safe when rendering templates. One careless mistake can lead to a full-blown server takeover.