Server-side template injection (SSTI)

SSTI

SSTI occurs when untrusted input is interpreted as template source instead of ordinary data. It can expose application objects and secrets and, in many template engines, lead to server-side code execution.

Also known as
Template injection · Server template injection · Expression language injection
Typical severity · High to Critical
Last updated
On this page: What it is
  1. 1 · What it is
  2. 2 · Why it matters
  3. 3 · Examples
  4. 4 · How to fix it
  5. 5 · How to test for it
  6. References
  7. Related

What it is#

Template engines combine a fixed template with data to produce HTML, email, configuration, or text. SSTI occurs when attacker-controlled text becomes part of the template program itself.

Template syntax may support arithmetic, property access, method calls, includes, filters, or expression languages. What begins as a formatting feature can expose application objects, environment variables, files, or runtime execution primitives.

SSTI is distinct from cross-site scripting. XSS executes in the browser after rendering. SSTI is evaluated on the server before the response is sent, although the same feature can contain both flaws.

Why it matters#

  • Read secrets, configuration, environment variables, or other application data.
  • Access server-side objects and invoke unintended methods.
  • Execute operating-system commands in engines with powerful expression features or reachable gadgets.
  • Modify generated emails, documents, or configuration.
  • Pivot to internal services using the template worker's network access.
  • Compromise multiple tenants when templates share a privileged rendering service.
Typical severity

Usually high. Critical when the engine or reachable object graph permits code execution, or when a shared renderer has access to sensitive credentials and customer data. Actual severity depends on context: who is affected, what data or actions are reachable, and existing controls.

Examples#

✕ Vulnerablepython
# Vulnerable: user input is compiled as a Jinja template.
from flask import request, render_template_string

@app.get('/welcome')
def welcome():
    return render_template_string(request.args.get('message', 'Welcome'))
✓ Fixedpython
# Fixed: the template is static and input is passed only as data.
from flask import request, render_template

@app.get('/welcome')
def welcome():
    message = request.args.get('message', 'Welcome')
    return render_template('welcome.html', message=message)
Examplehtml
<!-- templates/welcome.html -->
<p>{{ message }}</p>

Autoescaping helps prevent HTML injection in this output context, but the SSTI fix is keeping message out of the template source.

How to fix it#

1. Keep templates static. Load reviewed templates from application resources. Pass all untrusted values through the template engine's data model.

2. Do not offer arbitrary template authoring. If customers need customization, provide structured fields, predefined blocks, and a limited formatting grammar rather than a general-purpose template language.

3. Use context-appropriate output encoding. Enable autoescaping for HTML and explicitly encode URL, JavaScript, CSS, and attribute contexts. This controls output injection but does not replace the SSTI fix.

4. Minimize the template context. Pass only necessary primitive values and narrow DTOs. Do not expose request objects, application containers, database sessions, filesystem helpers, or environment access.

5. Treat sandboxing as defense in depth. Template sandboxes have had bypasses and may not constrain resource exhaustion. Keep the renderer isolated even when sandbox mode is enabled.

6. Isolate high-risk rendering. Run customer-controlled document or email rendering in a low-privilege worker with no secrets, no unnecessary network access, a read-only filesystem, and strict CPU, memory, and time limits.

7. Restrict includes and loaders. Templates must not choose arbitrary local or remote include paths. Use a fixed loader rooted in an application-owned directory.

8. Review expression features. Disable method invocation, dynamic property access, reflection, or unsafe filters when the engine supports such controls.

How to test for it#

  • Identify every feature that renders email, documents, themes, notifications, previews, reports, or user-configurable messages.
  • Submit a harmless template arithmetic marker, such as the engine's equivalent of 7 * 7, in a test environment. The output should remain literal text.
  • Test template delimiters for each engine actually used; do not assume all rendering paths share the same engine.
  • Verify error responses do not reveal template engine names, object types, filesystem paths, or expression evaluation details.
  • Review the context passed to templates and confirm it contains only required data objects.
  • Confirm include, import, and loader functions cannot leave the approved template directory or fetch remote content.
  • Exercise rendering limits with long and deeply nested benign templates in an isolated environment.
  • Add regression tests that render delimiter characters as data and assert that no expression evaluation occurs.

References#

Permalinklibrary.infinity8labs.com/server-side-template-injection/JSON
Found this in Breachvane? Your finding links here for background. Evidence, affected assets and retest status are in your Breachvane ↗ (opens in a new tab) workspace. Need help fixing it? Talk to us.