SSTI labs
Server-Side Template Injection (SSTI)
What is SSTI?
SSTI happens when user input is concatenated into a server-side rendered template (Jinja2, Twig, FreeMarker, Velocity, Handlebars). It almost always escalates to RCE because template engines have access to the host language runtime.
Why practice SSTI?
SSTI is one of the highest-impact vulnerabilities: quick detection with `{{7*7}}` or `${7*7}`, near-guaranteed escalation to RCE. It shows up in templated emails, custom error pages and configurable CMS. Frequent P1 bounties in mature programs.
What will you learn with the SSTI labs?
You'll learn the detection flow (basic + polyglot payloads), engine fingerprinting (Jinja2 vs Twig vs FreeMarker have different syntax), engine-specific RCE escalation (`__class__.__mro__` in Python, `Runtime.getRuntime()` in Java), and bypassing weak sandboxes.
Types of SSTI we cover
- Jinja2 / Flask
{{7*7}} → 49. Escalation with {{config.__class__.__init__.__globals__[...]}} → os.system.
- Twig / Symfony
{{7*7}} → 49. Escalation with {{_self.env.registerUndefinedFilterCallback('exec')}}.
- FreeMarker / Velocity
Java-based. ${7*7} or ${'a'?upper_case}. RCE via freemarker.template.utility.Execute.
- Handlebars / Node
More restrictive, but escalable with prototype pollution or exposed helpers.
How do you find and exploit SSTI?
A practical playbook — from recon to proof of concept.
- 1
Detect the injection
Drop a math expression in several engine syntaxes. If the response returns the evaluated result (not the literal), there's SSTI.
{{7*7}} → 49 · ${7*7} → 49 · #{7*7} · <%= 7*7 %> - 2
Tell SSTI apart from XSS
Use a polyglot that breaks in any context and watch what evaluates server-side versus what merely reflects.
${{<%[%'"}}%\ - 3
Fingerprint the engine
Each engine responds differently to specific payloads. This defines the RCE escalation chain.
{{7*'7'}} → 7777777 (Jinja2) | 49 (Twig) - 4
Escalate to RCE in Jinja2
Walk the Python object hierarchy up to os to run commands. The cycler/lipsum gadget is one of the most reliable.
{{cycler.__init__.__globals__.os.popen('id').read()}} - 5
Escalate to RCE in other engines
In Twig use the filter callback; in FreeMarker the Execute utility. Adapt the payload to the detected engine.
Twig: {{['id']|filter('system')}} · FreeMarker: ${"freemarker.template.utility.Execute"?new()("id")}
Loading labs...
View in full catalog →Frequently asked questions
What is SSTI?
Server-Side Template Injection happens when user input is inserted into a template rendered on the server (Jinja2, Twig, FreeMarker). The engine evaluates the payload as code, which almost always lets you escalate to remote command execution.
How is an SSTI exploited?
First you detect it with expressions like {{7*7}} that return 49. Then you fingerprint the engine and use its specific chain to reach the runtime: in Jinja2 you navigate __class__/__globals__ up to os.popen to run commands.
How do you find SSTI in bug bounty?
Try template payloads in any field that ends up rendered: profile names, email subjects, invoice templates, customizable error pages and CMS. If {{7*7}} or ${7*7} returns 49, you have SSTI.
How do you prevent SSTI?
Never build templates by concatenating user input; pass data as context variables, use sandboxed logic-less engines (like Mustache) for user content, and restrict the objects/attributes reachable from the template.
Difference between SSTI and XSS?
SSTI evaluates on the server and usually ends in RCE; XSS runs in the victim's browser. The same reflected input can be only XSS (if only the browser interprets it) or SSTI (if the template engine evaluates it first): {{7*7}}=49 confirms it.
Other related categories
- hunters training
- 650
- labs from real reports
- 50
- completions
- 380
- in bounties practiced
- $200,000
hunters training
labs from real reports
completions
in bounties practiced
Practice SSTI in real labs
Every SSTI lab replicates a real bug bounty report that got paid. Create your free account, start with the Academy and move to the labs whenever you want.
No card · free Academy · cancel anytime