Abstract neon data streams colliding with code-filled server panels

Server-Side Template Injection (SSTI): How a Simple Template Bug Can Turn Into Server-Side Code Execution

spyboy's avatarPosted by

A website asks for your name.

You enter:

John

The server generates:

Hello John

Nothing unusual.

But what happens if the application doesn’t simply treat your input as text?

What if your input becomes part of a server-side template?

Instead of:

Hello John

the application may actually be processing something conceptually like:

Hello {{ name }}

And if an attacker can inject template syntax into a location where the server evaluates it, the result can be far more serious than ordinary input manipulation.

The attacker may potentially be able to:

  • Access server-side template variables
  • Read information exposed to the template
  • Interact with application objects
  • Escape the intended template context
  • Access sensitive server-side functionality
  • In vulnerable configurations, achieve server-side code execution

This class of vulnerability is called:

Server-Side Template Injection

Or simply:

SSTI

It is one of those vulnerabilities where a seemingly harmless feature—such as a custom email, invoice, notification, profile page, or document template—can become a security boundary problem.

And unlike normal client-side template injection, SSTI happens on the server.

That distinction matters.


What Is Server-Side Template Injection?

To understand SSTI, first understand what a template engine does.

Developers frequently use templates to generate dynamic content.

For example, an application might have:

Hello, {{ username }}!

The template engine receives:

username = "John"

and renders:

Hello, John!

A simplified process looks like:

Template
Template Engine
Application Data
Rendered Output

This is perfectly normal.

The problem appears when untrusted user input becomes part of the template itself, rather than simply being supplied as data.

The safe concept is:

Template:
Hello, {{ username }}
Data:
username = user_input

The dangerous concept is:

Template:
Hello, USER_INPUT

where USER_INPUT is inserted into the template source and then interpreted by the template engine.

The difference is subtle.

The security impact can be enormous.


Data vs Template Code

This is the most important concept to understand.

Imagine an application wants to display:

Welcome, Alice

A secure implementation might have:

Template:
Welcome, {{ username }}
username:
Alice

The template engine understands that:

{{ username }}

is an instruction to retrieve a value.

The user’s value is only data.

Now imagine the application dynamically constructs the template:

Welcome, [USER INPUT]

and then passes the entire result into the template engine.

If the user submits template syntax, the engine may interpret it.

For example, a harmless mathematical expression in a testing environment might be:

{{ 7 * 7 }}

If the application returns:

49

rather than:

{{ 7 * 7 }}

you have evidence that the input is being evaluated by a template engine.

That is the basic SSTI discovery concept.


Why SSTI Is Different From XSS

SSTI is frequently confused with Cross-Site Scripting.

They’re not the same vulnerability.

XSS

XSS generally causes attacker-controlled content to execute in the victim’s browser.

Conceptually:

Attacker Input
Web Application
Victim Browser
JavaScript Execution

SSTI

SSTI occurs when attacker-controlled content is interpreted by a server-side template engine.

Conceptually:

Attacker Input
Web Application
Template Engine
Server-Side Evaluation
Rendered Response

The execution environment is different.

With XSS, the browser is generally the execution environment.

With SSTI, the server-side application is the important environment.


Where Can SSTI Appear?

SSTI doesn’t only occur on obvious template pages.

Potential locations include:

  • Custom email templates
  • Notification systems
  • PDF generators
  • Invoice generators
  • Report generators
  • CMS systems
  • Error pages
  • Profile customization
  • Document-generation systems
  • Customer-facing templates
  • Marketing platforms
  • Helpdesk systems
  • Admin dashboards
  • Webhooks
  • Dynamic HTML generation
  • Server-rendered applications

One particularly interesting category is applications that allow users to customize content.

For example:

Email subject:
{{ customer_name }} - Your Order
Email body:
Hello {{ customer_name }},
your order {{ order_id }} is ready.

A template feature inherently needs an interpreter.

That means the security model around that interpreter becomes important.


Common Template Engines

Different programming languages and frameworks use different template engines.

Examples include:

Python

  • Jinja2
  • Mako
  • Tornado templates

Java

  • FreeMarker
  • Velocity
  • Thymeleaf

PHP

  • Twig
  • Smarty

JavaScript / Node.js

  • Handlebars
  • Pug
  • EJS
  • Nunjucks

Ruby

  • ERB
  • Slim
  • Haml

.NET

  • Razor

The syntax differs between engines.

For example, one engine might use:

{{ value }}

while another might use:

<%= value %>

or another syntax entirely.

Therefore:

Seeing template-like syntax does not automatically tell you which engine is being used.


The First Step: Detect Template Evaluation

When testing an application you own or are explicitly authorized to assess, don’t immediately jump into advanced exploitation.

Start with a simple question:

Is my input actually being interpreted?

A harmless mathematical expression can help.

For a template syntax that uses double curly braces, a researcher might test:

{{ 7 * 7 }}

If the application returns:

49

that’s interesting.

If it returns:

{{ 7 * 7 }}

the input may simply be reflected as text.

The distinction is important.


Reflection vs Evaluation

Consider three possible responses.

Case 1 — Reflection

Input:

{{ 7 * 7 }}

Output:

{{ 7 * 7 }}

The application may simply be displaying the input.

No evidence of template evaluation.


Case 2 — Evaluation

Input:

{{ 7 * 7 }}

Output:

49

This strongly suggests that a template engine interpreted the expression.


Case 3 — Sanitization

Input:

{{ 7 * 7 }}

Output:

7 * 7

The application may have removed or transformed the template syntax.

Again, this isn’t proof of SSTI by itself.


Don’t Stop at “It Evaluates”

Finding expression evaluation is an important discovery.

But security testing should continue with a key question:

What privileges does the template have?

A template engine might deliberately expose only a tiny collection of safe variables.

For example:

username
order_id
company_name

If the engine provides no dangerous objects or functions, the impact may be limited.

Another application could expose powerful server-side objects.

That can dramatically change the risk.


Template Context Matters

Imagine the application gives a template access to:

user
order
company

That’s different from giving it access to:

request
session
environment
filesystem
application
database
runtime

The more powerful the template context, the more carefully it needs to be isolated.

A useful security question is:

What objects can this template access?

The SSTI Attack Surface

Think about the application as a chain:

User Input
Input Storage
Template Construction
Template Parser
Template Evaluation
Application Context
Rendered Output

The vulnerability usually appears because untrusted input crosses a boundary it shouldn’t cross.

The application effectively turns:

Data

into:

Code interpreted by a template engine

That is the fundamental problem.


Why SSTI Can Become Serious

The severity of SSTI varies significantly.

A vulnerable application might allow:

Expression evaluation

but nothing more.

Another could expose:

Application objects

Another might allow access to:

Sensitive configuration

And in particularly dangerous configurations, template evaluation can cross into:

Server-side code execution

This is why SSTI shouldn’t automatically be classified as “remote code execution.”

The actual impact must be demonstrated.


SSTI Is Not Automatically RCE

This is an important distinction for bug bounty reports.

Finding:

{{ 7 * 7 }}

evaluating to:

49

doesn’t automatically prove:

Remote Code Execution.

It proves that some form of expression evaluation appears possible.

To establish greater impact, you need evidence showing what the template context permits.

A professional report should distinguish between:

Template expression evaluation

and:

Arbitrary server-side code execution

Don’t claim the latter without evidence.


A Safe SSTI Testing Methodology

If you’re testing an authorized application, use a gradual methodology.

Step 1 — Find Dynamic Input

Look for functionality involving:

  • Templates
  • Emails
  • Reports
  • Notifications
  • Documents
  • Custom messages
  • Dynamic pages

Step 2 — Identify Reflection

Submit a unique harmless marker:

SSTI_TEST_12345

Determine where it appears.


Step 3 — Test Template Syntax

Use a harmless expression appropriate to the suspected template syntax.

For example:

{{ 7 * 7 }}

Do not begin with destructive payloads.


Step 4 — Compare Responses

Determine whether the result is:

Reflected

or:

Evaluated

Step 5 — Identify the Engine

Look for clues from:

  • Error messages
  • Stack traces in a controlled test environment
  • Framework documentation
  • Application technology
  • Template syntax
  • Response behavior

Don’t assume the engine based on syntax alone.


Step 6 — Determine the Template Context

Ask:

What variables are available?

and:

What functionality can the template access?

Step 7 — Determine Impact

Possible findings might range from:

Expression evaluation

to:

Sensitive server-side data exposure

to:

Server-side code execution

The report should accurately describe the highest impact you can safely demonstrate.


A Simple Lab Example

Suppose you’re running a deliberately vulnerable application locally.

The application contains:

template = user_input

and then passes that value into a template engine.

You submit:

Hello {{ 7 * 7 }}

The server returns:

Hello 49

You’ve established:

User Input
Template Engine
Expression Evaluation

That’s a useful starting point.

In a controlled lab, you can then study how the particular template engine handles variables, objects, functions, and sandbox restrictions.

The key is to understand the behavior rather than blindly copying exploit strings from the internet.


Why Template Sandboxing Matters

Some template engines provide sandboxing mechanisms.

The idea is:

Template
Restricted Environment
Limited Objects
Limited Operations

Instead of giving the template unrestricted access to the application runtime.

This can reduce the impact of a compromised template.

But sandboxing is not something developers should blindly assume makes arbitrary template execution safe.

Security depends on:

  • Engine implementation
  • Version
  • Configuration
  • Available objects
  • Exposed functions
  • Extensions
  • Application integration

A sandbox should be treated as a security boundary that needs careful design and testing.


The Dangerous Developer Pattern

One of the most important patterns to understand is:

render(user_input)

versus:

render(
fixed_template,
user_input=user_input
)

The first can mean:

Treat the user’s input as template source.

The second means:

Treat the user’s input as data inserted into a trusted template.

Those are fundamentally different.


Secure Pattern

Instead of dynamically constructing:

template = "Hello " + user_input

and then interpreting the resulting string as a template, use a fixed template:

Hello {{ username }}

and pass:

username = user_input

The template is trusted.

The value is untrusted.

This separation is critical.


Never Build Templates From Raw User Input

A dangerous pattern looks conceptually like:

template_source = database_value
render(template_source)

if users can control database_value.

This creates a dangerous trust boundary.

A safer design is:

Trusted Template
+
User Data
Template Engine
Output

The template source should be controlled by trusted code or by a carefully designed template system.


SSTI and Stored Data

SSTI doesn’t necessarily require immediate reflection.

Consider:

User submits profile template
Application stores it
Admin opens profile
Server renders template

Now the vulnerability is stored.

The attacker may not see the result immediately.

This can make the issue harder to identify.


SSTI in Email Systems

Imagine a customer-support application allows employees to create email templates.

A legitimate template might contain:

Hello {{ customer_name }}

The system stores the template.

Later, the server renders it for customers.

If an untrusted user can create or modify the template and the rendering environment exposes powerful functionality, the security boundary may be broken.

This is why template-management permissions matter.


SSTI in PDF Generators

Dynamic PDF systems are another interesting area.

For example:

Invoice data
HTML/template
Template engine
PDF renderer

If user-controlled content becomes template source rather than data, the rendering pipeline can become an attack surface.

The same principle applies to:

  • Reports
  • Certificates
  • Receipts
  • Statements
  • Export documents

SSTI in CMS Platforms

Content management systems often support:

Dynamic variables
Custom themes
Templates
Widgets
Page builders

This creates a natural template-processing environment.

The important security question is:

Who is allowed to create or modify templates?

If only trusted administrators can modify template source, the risk model is very different from a system where ordinary users can submit arbitrary templates.


SSTI vs Client-Side Template Injection

There is another important distinction.

Client-Side Template Injection

The browser processes the template.

Browser
JavaScript Framework
Template

Server-Side Template Injection

The server processes the template.

Server
Template Engine
Rendered Response

The same application can potentially contain both classes of problems.

Always determine:

Where is the template being evaluated?


Finding SSTI During Recon

SSTI isn’t usually discovered by simply scanning every endpoint for one magic payload.

Recon helps identify likely locations.

Look for parameters or functionality involving words such as:

template
view
layout
theme
email
message
notification
render
preview
report
document
invoice
custom
content
subject
body

These aren’t vulnerabilities.

They’re simply clues.

For example:

POST /email/preview

is potentially more interesting for SSTI research than:

GET /static/logo.png

because the endpoint explicitly performs rendering.


Analyze Application Behavior

Suppose you discover:

POST /preview

with:

{
"template": "Hello {{name}}"
}

That tells you something important.

The application accepts a template.

Now ask:

Who can submit it?
Who can modify it?
Where is it rendered?
Which engine processes it?
What objects are exposed?
Is it sandboxed?

These questions are much more valuable than simply throwing payloads at the endpoint.


SSTI and Authentication Boundaries

Suppose normal users can create templates.

But the template rendering service runs with the privileges of a highly privileged backend service.

Now you potentially have:

Low-privileged user
Template input
Privileged renderer

That privilege mismatch is important.

Security isn’t just about whether template syntax executes.

It’s also about:

Under whose authority does it execute?


SSTI and Multi-Tenant Applications

Multi-tenant SaaS applications deserve particular attention.

Imagine:

Tenant A
Template
Shared Rendering Service

If templates can access objects belonging to:

Tenant B

the issue could become a cross-tenant data exposure vulnerability.

This is why authorization must continue all the way through template rendering.


Don’t Ignore Authorization

Consider:

Template Engine
customer object
customer.email

If the application assumes:

“The template author is allowed to see everything available to the renderer”

you could have an authorization problem.

Template access should follow the same security model as the rest of the application.


Common SSTI Mistakes Developers Make

❌ Treating user input as template source

This is the fundamental mistake.

❌ Assuming escaping solves SSTI

HTML escaping protects against certain output contexts.

It does not necessarily prevent a template engine from interpreting template syntax.

❌ Exposing powerful objects

Giving templates unrestricted application objects increases risk.

❌ Running the renderer with excessive privileges

If the renderer is compromised, excessive privileges increase the blast radius.

❌ Trusting stored templates

Data stored in a database can still be attacker-controlled.

❌ Ignoring multi-tenant boundaries

Template rendering must respect tenant authorization.

❌ Assuming sandboxing is automatically perfect

Sandboxing needs careful configuration and maintenance.


How to Prevent SSTI

The strongest defense is simple:

Don’t interpret untrusted input as template code.

Instead:

Trusted Template
+
Untrusted Data
Safe Rendering

not:

Untrusted Input
Template Source
Evaluation

Use Trusted Templates

Prefer templates that are controlled by the application:

Hello {{ username }}

Then provide:

username = user_input

Don’t allow arbitrary template source unless the feature genuinely requires it.


If Users Need Templates

Sometimes users legitimately need custom templates.

For example:

  • Enterprise email templates
  • Notification systems
  • Branding
  • Reports
  • Documents

In that case, create a deliberately restricted template language.

Instead of exposing the entire programming environment, allow only specific variables:

{{ customer.name }}
{{ order.id }}
{{ order.total }}

and nothing beyond the intended data model.


Use Allowlisting

Instead of:

Anything available to the runtime

use:

Allowed:
customer.name
customer.email
order.id
order.total

Everything else should be unavailable.

This drastically reduces the template’s capabilities.


Separate Rendering From Application Privileges

If a template renderer needs to process user-controlled templates, consider isolating it.

Conceptually:

Main Application
Template Service
Restricted Environment
Rendered Output

The renderer should have:

  • Minimal permissions
  • Minimal filesystem access
  • Minimal network access
  • No unnecessary secrets
  • Restricted credentials
  • Appropriate resource limits

The goal is:

Minimize blast radius.


Don’t Expose Secrets to Templates

Avoid making these directly available:

Environment variables
API keys
Database credentials
Cloud credentials
Session secrets
Signing keys
Internal tokens

A template should receive the smallest possible data set.

If a template only needs:

customer_name

don’t give it:

entire_application_context

Keep Dependencies Updated

Template engines are software.

They can contain security vulnerabilities.

Track:

  • Template engine versions
  • Framework versions
  • Plugins
  • Extensions
  • Sandboxing components

A secure configuration combined with an outdated vulnerable component can still create risk.


Add Security Tests

Applications that support custom templates should include security tests covering:

  • Template injection
  • Unauthorized variable access
  • Cross-tenant access
  • Sandbox escapes
  • Sensitive data exposure
  • Resource exhaustion
  • Unexpected function access

Security testing should happen before deployment rather than after an attacker discovers the problem.


How Bug Bounty Hunters Should Report SSTI

A good report should clearly explain:

1. Injection point

Where the input enters the application.

2. Template behavior

What proves that the input is being evaluated.

3. Template engine

If confidently identified.

4. Context

What variables or objects are accessible.

5. Impact

What you actually demonstrated.

6. Reproduction steps

A minimal, reliable sequence.

7. Remediation

How the application can separate data from template source.


Example Safe Finding

Imagine:

POST /preview

with:

{
"name": "{{ 7 * 7 }}"
}

The server responds:

49

A report might explain:

The name parameter is interpreted as a server-side template expression.
A harmless arithmetic expression evaluates on the server, demonstrating
server-side template evaluation.

That’s much better than immediately claiming:

RCE!!!

without evidence.


Severity Depends on Impact

SSTI doesn’t have one universal severity.

Consider:

Low impact

Only harmless expressions are evaluated.

Higher impact

The template can access sensitive application data.

Serious impact

The template can access privileged functionality or sensitive secrets.

Critical impact

Arbitrary server-side code execution is demonstrated.

The actual impact depends on the application, template engine, privileges, and available objects.


A Practical SSTI Checklist

For authorized testing:

Discovery

  • Find dynamic rendering functionality
  • Identify template-related parameters
  • Look for preview functionality
  • Identify custom email/report/document features

Detection

  • Test harmless template expressions
  • Compare reflection vs evaluation
  • Check response behavior
  • Review controlled error messages

Identification

  • Determine the likely template engine
  • Identify supported syntax
  • Identify available variables
  • Determine whether sandboxing is enabled

Impact

  • Determine what data is accessible
  • Check authorization boundaries
  • Determine whether sensitive objects are exposed
  • Establish the maximum impact safely

Reporting

  • Document the injection point
  • Include harmless proof
  • Explain the trust-boundary failure
  • Describe demonstrated impact
  • Provide remediation

A Simple Mental Model

Whenever you see a template system, remember:

                 TRUSTED
                    │
                    ▼
             Template Source
                    │
                    ▼
              Template Engine
                    ▲
                    │
              Application Data
                    ▲
                    │
              UNTRUSTED INPUT

The important boundary is:

Template Source ≠ User Data

If the application accidentally mixes them:

User Data
Template Source
Template Evaluation

you may have SSTI.


SSTI Attack Chain

A simplified vulnerable architecture looks like:

Attacker Input
User-Controlled Parameter
Template Construction
Template Engine
Server-Side Evaluation
├── Application Objects
├── Sensitive Data
└── Server Functionality

A secure architecture looks more like:

Trusted Template
Template Engine
Validated Application Data
Rendered Output

That separation is the entire game.


SSTI vs Other Injection Vulnerabilities

It helps to compare the underlying idea.

VulnerabilityInterpreterTypical Location
SQL InjectionSQL engineDatabase
Command InjectionOS command interpreterServer
XSSBrowserClient
SSTITemplate engineServer
LDAP InjectionLDAP interpreterDirectory service
XPath InjectionXPath engineXML data

The common theme is:

Untrusted input reaches an interpreter in a way the developer didn’t intend.

That is why learning one injection vulnerability helps you understand others.


The Real Lesson Behind SSTI

SSTI isn’t fundamentally about memorizing template syntax.

It’s about understanding trust boundaries.

A developer may think:

"It's just a string."

But the server may think:

"This is template code."

Those two interpretations are very different.

The security vulnerability exists in the gap between them.


What Security Researchers Should Look For

When you encounter a dynamic rendering feature, ask:

What is the input?

Then:

Where does it go?

Then:

Is it treated as data?

Then:

Or does an interpreter process it?

Then:

What privileges does that interpreter have?

Finally:

What can the resulting execution actually access?

That sequence is far more useful than memorizing hundreds of payloads.


What Developers Should Remember

If your application supports templates:

Never assume user-controlled strings are harmless.

Keep these concepts separate:

Template = Code / Instructions
User Input = Data

Use trusted templates.

Pass user values as variables.

Restrict the available template context.

Avoid exposing sensitive objects.

Sandbox where appropriate.

Run rendering components with minimal privileges.

And test the complete rendering pipeline.


Final Takeaway

Server-Side Template Injection begins with a deceptively simple mistake:

Treating untrusted data as trusted template code.

The dangerous flow is:

User Input
Template Source
Template Engine
Server-Side Evaluation

The safer flow is:

Trusted Template
+
Untrusted Data
Controlled Rendering
Output

For security researchers, the key isn’t to throw the biggest payload at a parameter.

Start small.

Find where the input goes.

Determine whether it is actually evaluated.

Identify the template engine.

Understand the available context.

Then establish the real impact using safe, authorized testing.

For developers, the lesson is even simpler:

Never let untrusted users decide what your server should interpret as template code.

A string can look harmless.

A template engine can turn that string into instructions.

And once data crosses that boundary, the application may be doing much more than simply displaying text.

Think Like an Attacker. Secure Like a Pro.


Discover more from Spyboy blog

Subscribe to get the latest posts sent to your email.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.