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 OrderEmail 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:
usernameorder_idcompany_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:
userordercompany
That’s different from giving it access to:
requestsessionenvironmentfilesystemapplicationdatabaseruntime
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_valuerender(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:
templateviewlayoutthemeemailmessagenotificationrenderpreviewreportdocumentinvoicecustomcontentsubjectbody
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.namecustomer.emailorder.idorder.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 variablesAPI keysDatabase credentialsCloud credentialsSession secretsSigning keysInternal 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, demonstratingserver-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.
| Vulnerability | Interpreter | Typical Location |
|---|---|---|
| SQL Injection | SQL engine | Database |
| Command Injection | OS command interpreter | Server |
| XSS | Browser | Client |
| SSTI | Template engine | Server |
| LDAP Injection | LDAP interpreter | Directory service |
| XPath Injection | XPath engine | XML 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 / InstructionsUser 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.
