Skip to content
Server-Side Template Injection

Server-Side Template Injection

Server-Side Template Injection (SSTI) happens when user input is concatenated into a server-side template instead of being passed as data. The template engine then evaluates the injected expression, which often leads to secret disclosure or remote code execution.

It is most common with Python’s Jinja2 (Flask), but the same idea applies to Twig (PHP), Freemarker and Velocity (Java), ERB (Ruby) and others.

Detection

Inject a simple arithmetic expression and check whether it is evaluated:

  • {{7*7}} returning 49 indicates Jinja2 or Twig (note that {7*7} is not evaluated, the double braces are).
  • Probes that render differently per engine help identify it: ${7*7} (Freemarker, JSP EL), <%= 7*7 %> (ERB), #{7*7} (Thymeleaf). A returned 49 confirms server-side evaluation.

The tplmap and SSTImap tools automate detection and exploitation.

Jinja2 exploitation

Jinja2 (the Python engine used by Flask) processes data in a sandbox, but a few tricks still allow reading secrets or reaching RCE. The goal is to walk the Python object graph from any reachable object up to dangerous modules. See HackTricks for a full list of payloads.

  • Read configuration and environment variables (often enough to grab a flag):

    {{ config }}                              # Flask config object
    {{ config.items() }}
    {{ lipsum.__globals__.os.environ }}       # all environment variables
    {{ lipsum.__globals__.os.environ['FLAG'] }}
  • Remote code execution through a global that already exposes os:

    {{ cycler.__init__.__globals__.os.popen('id').read() }}
    {{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

    lipsum, cycler and joiner are built-in Jinja globals whose __globals__ already contain os, which avoids the longer __subclasses__() chain.

  • When those are filtered, reach any class through the method resolution order:

    {{ ''.__class__.__mro__[1].__subclasses__() }}   # then find subprocess.Popen, etc.

See PayloadsAllTheThings for engine-specific payloads and sandbox bypasses.

Reaching the template

When the template is rendered from a file and you cannot control the request data, you may still be able to overwrite the template file on the server. A classic delivery is to upload an archive that the server extracts, containing a symlink that escapes the extraction directory and points at a template file, so your injected {{ ... }} is rendered on the next visit.