What Is Server-Side Template Injection?
Template engines like Jinja2, Twig, Freemarker, and Velocity allow developers to embed expressions in templates β {{ user.name }} outputs the user's name. These expressions are evaluated server-side with full access to the application context.
SSTI occurs when user-controlled input is rendered directly by the template engine rather than being passed as a safe variable. The distinction is critical:
from jinja2 import Environment env = Environment() # SAFE: user input is a variable value β not rendered as template template = env.from_string("Hello, !") template.render(name=user_input) # user_input is data # VULNERABLE: user input is the template string itself template = env.from_string(f"Hello, {user_input}!") template.render() # user_input is rendered as template code
Impact: In Jinja2, SSTI with server-side evaluation commonly leads to full RCE via Python's __class__.__mro__ chain. Freemarker SSTI can execute arbitrary Java code. This is a critical severity finding in virtually every penetration test.
Jinja2 SSTI
Jinja2 is used in Flask, Django (with Jinja2 backend), Ansible, and many Python applications. The detection probe is simple: if the input {{7*7}} produces 49 in the response, the application is vulnerable.
# Detection probe β renders to 49 if vulnerable {{7*7}} # Config object exposure {{config}} # Environment access via class chain {{"".__class__.__mro__[1].__subclasses__()}}
Twig SSTI (PHP)
Twig is the default template engine for Symfony and many PHP frameworks. Detection probe: {{7*7}} renders to 49. Twig's sandbox mode limits available functions, but misconfigured sandboxes can still be escaped.
Freemarker (Java): Detection probe is ${7*7}. Exploitable via <#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")} β this directly calls Runtime.exec() in Java. Freemarker SSTI is classified as critical in every CVSS scoring.
Detection Techniques
- Arithmetic probes:
{{7*7}},${7*7},#{7*7}β if the response contains 49, template is rendering user input - Engine fingerprinting: Different engines use different syntax.
{{7*'7'}}returns49in Twig but7777777in Jinja2 β tells you which engine is in use - tplmap: Automated SSTI detection and exploitation tool, similar to sqlmap but for template injection
- SAST: Search for
render_template_string,Environment().from_string, or any template rendering function that accepts user input as the template string
Prevention
from jinja2 import Environment, select_autoescape env = Environment(autoescape=select_autoescape(["html"])) # CORRECT: template string is from your codebase, not user input # User input only appears as variable values template = env.from_string("Hello, ! You searched for: ") output = template.render( name=user.name, # safe variable query=user_query # safe variable β not template code )
The rule: the template string must come from your code or a trusted configuration store β never from user input. User input goes only into template variables, where it is treated as data, not code.
If you genuinely need user-defined templates (e.g., email template customisation), use a logic-less template engine like Mustache or Handlebars, which have no expression evaluation capability.
Detect SSTI in Your Codebase
AquilaX SAST detects user input passed to template rendering functions β the root cause of server-side template injection vulnerabilities.
Start Free Scan